foliko 1.0.30 → 1.0.32

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.
@@ -1,78 +1,81 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(grep -r \"dotenv\" \"D:/Code/vb-agent/\" --include=\"*.json\" 2>/dev/null | head -20)",
5
- "Bash(cd \"D:/Code/vb-agent\" && pnpm add dotenv)",
6
- "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(Object.keys\\(ai\\).filter\\(k => k.toLowerCase\\(\\).includes\\('reason'\\) || k.toLowerCase\\(\\).includes\\('split'\\) || k.toLowerCase\\(\\).includes\\('think'\\)\\)\\)\")",
7
- "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(typeof ai.extractReasoningMiddleware, ai.extractReasoningMiddleware.toString\\(\\).substring\\(0, 500\\)\\)\")",
8
- "Bash(cd \"D:/Code/vb-agent\" && node -e \"\nconst ai = require\\('ai'\\);\nconsole.log\\('=== extractReasoningMiddleware ==='\\);\nconsole.log\\(ai.extractReasoningMiddleware.toString\\(\\)\\);\nconsole.log\\('\\\\n=== isReasoningUIPart ==='\\);\nconsole.log\\(typeof ai.isReasoningUIPart\\);\n\")",
9
- "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(Object.keys\\(ai\\).filter\\(k => k.toLowerCase\\(\\).includes\\('split'\\)\\)\\)\")",
10
- "Bash(cd \"D:/Code/vb-agent\" && pnpm add ink react)",
11
- "Bash(cd \"D:/Code/vb-agent\" && node cli/bin/foliko.js --help)",
12
- "Bash(cd \"D:/Code/vb-agent\" && timeout 2 node cli/bin/foliko.js chat 2>&1 || true)",
13
- "Bash(cd \"D:/Code/vb-agent\" && node -e \"const mt = require\\('marked-terminal'\\); console.log\\(typeof mt, Object.keys\\(mt\\)\\)\")",
14
- "Bash(cd \"D:/Code/vb-agent\" && timeout 3 node cli/bin/foliko.js chat 2>&1 || true)",
15
- "Bash(echo \"你好\" | node cli/bin/foliko.js chat 2>&1 | head -50)",
16
- "Bash(printf '思考一下1+1等于几\\\\n' | node cli/bin/foliko.js chat 2>&1 | head -100)",
17
- "Bash(printf '1+1等于几\\\\n' | node cli/bin/foliko.js chat 2>&1)",
18
- "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\nconsole.log\\(renderLine\\('🎉 这是一个 emoji'\\)\\)\nconsole.log\\(renderLine\\('**粗体** 和 🎉'\\)\\)\n\")",
19
- "Bash(node -e \"\nconst { render } = require\\('./cli/src/utils/markdown'\\)\nconst text = '🎉 今天是个好日子\\\\n## 标题\\\\n- 列表项1\\\\n- 列表项2'\nconsole.log\\(render\\(text\\)\\)\n\")",
20
- "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\n\n// 模拟emoji被截断的情况\nconst emoji = '🎉'\nconsole.log\\('完整的 emoji:', renderLine\\(emoji\\)\\)\n\n// 模拟截断 - emoji的UTF-8字节是 \\\\xF0\\\\x9F\\\\x8E\\\\x89\nconst partial = '\\\\xF0\\\\x9F' // 不完整的emoji\nconsole.log\\('截断的 emoji:', renderLine\\(partial\\)\\)\n\")",
21
- "Bash(node -e \"\nconst isIncompleteUTF8 = \\(str\\) => {\n if \\(!str || str.length === 0\\) return false\n const lastChar = str.charCodeAt\\(str.length - 1\\)\n if \\(lastChar >= 0x80 && lastChar < 0xC0\\) return true\n return false\n}\n\n// 测试各种emoji\nconst tests = ['🎉', '📁', '⚡', '🛠️', '🔑', '💾', '📝', '⏰', '🔌', '📋']\ntests.forEach\\(e => {\n console.log\\(e, '完整:', !isIncompleteUTF8\\(e\\)\\)\n}\\)\n\n// 测试被截断的emoji \\(只保留第一字节\\)\nconst broken = '🎉'.slice\\(0, 1\\)\nconsole.log\\('截断emoji:', broken, '检测:', isIncompleteUTF8\\(broken\\)\\)\n\")",
22
- "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\nconsole.log\\('测试:', renderLine\\('📁 文件操作:读取、创建'\\)\\)\n\")",
23
- "Bash(node -e \"\nconst hasIncompleteSurrogate = \\(str\\) => {\n if \\(!str || str.length === 0\\) return false\n const lastChar = str.charCodeAt\\(str.length - 1\\)\n console.log\\('检查:', str, 'lastChar:', lastChar.toString\\(16\\), '范围:', \\(lastChar >= 0xD800 && lastChar <= 0xDBFF\\)\\)\n return lastChar >= 0xD800 && lastChar <= 0xDBFF\n}\n\nconst chunk1 = '\\\\xD83C'\nconsole.log\\('结果:', hasIncompleteSurrogate\\(chunk1\\)\\)\n\")",
24
- "Bash(cd D:/Code/vb-agent && node test-stream-emoji.js 2>&1)",
25
- "Read(//d/Date/20260321/app/**)",
26
- "Bash(node -e \":*)",
27
- "Bash(node -e \"\nconst { loadAgentConfig } = require\\('./plugins/default-plugins'\\);\nconst config = loadAgentConfig\\('D:/Date/20260321/app/.agent'\\);\nconsole.log\\('skillsDirs:', config.skillsDirs\\);\n\")",
28
- "Bash(cd D:/Code/vb-agent && pnpm install --shamefully-hoist 2>&1 | head -20)",
29
- "Bash(cd D:/Code/vb-agent && rm -rf node_modules && pnpm install)",
30
- "Bash(cd D:/Code/vb-agent && timeout 8 node test-tg.js 2>&1 || true)",
31
- "Bash(cd D:/Code/vb-agent && timeout 10 node test-tg.js 2>&1 || true)",
32
- "Bash(find /d/Code/vb-agent -name \"*email*\" -type f 2>/dev/null | grep -v node_modules | grep -v .git)",
33
- "Bash(find /d/Code/vb-agent -maxdepth 2 -name \"*.md\" -type f 2>/dev/null | grep -v node_modules)",
34
- "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && echo \"Syntax OK\")",
35
- "Bash(node -c plugins/install-plugin.js && echo \"Syntax OK\")",
36
- "Bash(node -c cli/src/ui/chat-ui.js && echo \"Syntax OK\")",
37
- "Bash(node -c plugins/default-plugins.js && echo \"Syntax OK\")",
38
- "Bash(node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
39
- "Bash(node -c plugins/telegram-plugin.js && node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
40
- "Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")",
41
- "WebSearch",
42
- "Bash(npm ls:*)",
43
- "Bash(cd node_modules/@chnak/weixin-bot && npm run build 2>&1)",
44
- "Bash(cd node_modules/@chnak/weixin-bot && npx tsc 2>&1)",
45
- "Bash(npm install:*)",
46
- "Bash(cd node_modules/@chnak/weixin-bot && npx typescript --version && npx tsc 2>&1)",
47
- "Bash(npx tsc:*)",
48
- "Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js 2>&1)",
49
- "Bash(node --check /d/Code/vb-agent/plugins/telegram-plugin.js && node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
50
- "Bash(node --check /d/Code/vb-agent/cli/src/ui/chat-ui.js && echo \"OK\")",
51
- "Bash(npm uninstall:*)",
52
- "Bash(rm -rf /tmp/weixin-bot && git clone https://github.com/chnak/weixin-bot.git /tmp/weixin-bot 2>&1)",
53
- "Bash(mkdir -p node_modules/@chnak/weixin-bot && cp -r /tmp/weixin-bot/nodejs/* node_modules/@chnak/weixin-bot/ && cd node_modules/@chnak/weixin-bot && npm install 2>&1)",
54
- "Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
55
- "Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', Object.keys\\(m\\)\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\")",
56
- "Bash(npm run:*)",
57
- "Bash(node -e \"const { WeixinBot } = require\\('@chnak/weixin-bot'\\); console.log\\(typeof WeixinBot\\)\" 2>&1)",
58
- "Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', typeof m.WeixinBot\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\" 2>&1)",
59
- "Bash(ls -la D:/code/vb-agent/cli/bin/ && cat D:/code/vb-agent/cli/bin/*.js 2>/dev/null | head -50)",
60
- "Bash(npm config:*)",
61
- "Bash(node -c plugins/subagent-plugin.js 2>&1)",
62
- "Bash(node -c plugins/default-plugins.js 2>&1)",
63
- "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && node -c plugins/tools-plugin.js 2>&1)",
64
- "Bash(node -c src/core/plugin-base.js && node -c src/core/plugin-manager.js 2>&1)",
65
- "Bash(node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
66
- "Bash(node -c src/core/plugin-manager.js 2>&1)",
67
- "Bash(node -c src/core/plugin-manager.js && node -c src/core/plugin-base.js && node -c plugins/default-plugins.js && node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
68
- "Bash(node -e \"require\\('dotenv'\\).config\\(\\); console.log\\('PROVIDER:', process.env.FOLIKO_PROVIDER\\); console.log\\('MODEL:', process.env.FOLIKO_MODEL\\); console.log\\('KEY:', process.env.DEEPSEEK_API_KEY ? 'set' : 'not set'\\)\" 2>&1)",
69
- "Bash(node cli/src/index.js chat 2>&1 | head -30)",
70
- "Bash(node -e 'const { DEFAULT_PROVIDERS } = require\\(\"./src/core/provider\"\\); console.log\\(DEFAULT_PROVIDERS\\);')",
71
- "Bash(node -e \"const dotenv = require\\('dotenv'\\); const result = dotenv.config\\(\\); console.log\\('Result:', result\\); console.log\\('PROVIDER after dotenv:', process.env.FOLIKO_PROVIDER\\);\" 2>&1)",
72
- "Bash(node -c plugins/email.js && node -c plugins/default-plugins.js 2>&1)",
73
- "Bash(node -c plugins/email.js 2>&1)",
74
- "Bash(npm list:*)",
75
- "Bash(node -e \"const {EmailPlugin} = require\\('./plugins/email'\\); const p = new EmailPlugin\\(\\); console.log\\('email plugin loaded ok'\\); console.log\\('enabled:', p.enabled\\); console.log\\('version:', p.version\\);\" 2>&1)"
76
- ]
77
- }
78
- }
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep -r \"dotenv\" \"D:/Code/vb-agent/\" --include=\"*.json\" 2>/dev/null | head -20)",
5
+ "Bash(cd \"D:/Code/vb-agent\" && pnpm add dotenv)",
6
+ "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(Object.keys\\(ai\\).filter\\(k => k.toLowerCase\\(\\).includes\\('reason'\\) || k.toLowerCase\\(\\).includes\\('split'\\) || k.toLowerCase\\(\\).includes\\('think'\\)\\)\\)\")",
7
+ "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(typeof ai.extractReasoningMiddleware, ai.extractReasoningMiddleware.toString\\(\\).substring\\(0, 500\\)\\)\")",
8
+ "Bash(cd \"D:/Code/vb-agent\" && node -e \"\nconst ai = require\\('ai'\\);\nconsole.log\\('=== extractReasoningMiddleware ==='\\);\nconsole.log\\(ai.extractReasoningMiddleware.toString\\(\\)\\);\nconsole.log\\('\\\\n=== isReasoningUIPart ==='\\);\nconsole.log\\(typeof ai.isReasoningUIPart\\);\n\")",
9
+ "Bash(cd \"D:/Code/vb-agent\" && node -e \"const ai = require\\('ai'\\); console.log\\(Object.keys\\(ai\\).filter\\(k => k.toLowerCase\\(\\).includes\\('split'\\)\\)\\)\")",
10
+ "Bash(cd \"D:/Code/vb-agent\" && pnpm add ink react)",
11
+ "Bash(cd \"D:/Code/vb-agent\" && node cli/bin/foliko.js --help)",
12
+ "Bash(cd \"D:/Code/vb-agent\" && timeout 2 node cli/bin/foliko.js chat 2>&1 || true)",
13
+ "Bash(cd \"D:/Code/vb-agent\" && node -e \"const mt = require\\('marked-terminal'\\); console.log\\(typeof mt, Object.keys\\(mt\\)\\)\")",
14
+ "Bash(cd \"D:/Code/vb-agent\" && timeout 3 node cli/bin/foliko.js chat 2>&1 || true)",
15
+ "Bash(echo \"你好\" | node cli/bin/foliko.js chat 2>&1 | head -50)",
16
+ "Bash(printf '思考一下1+1等于几\\\\n' | node cli/bin/foliko.js chat 2>&1 | head -100)",
17
+ "Bash(printf '1+1等于几\\\\n' | node cli/bin/foliko.js chat 2>&1)",
18
+ "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\nconsole.log\\(renderLine\\('🎉 这是一个 emoji'\\)\\)\nconsole.log\\(renderLine\\('**粗体** 和 🎉'\\)\\)\n\")",
19
+ "Bash(node -e \"\nconst { render } = require\\('./cli/src/utils/markdown'\\)\nconst text = '🎉 今天是个好日子\\\\n## 标题\\\\n- 列表项1\\\\n- 列表项2'\nconsole.log\\(render\\(text\\)\\)\n\")",
20
+ "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\n\n// 模拟emoji被截断的情况\nconst emoji = '🎉'\nconsole.log\\('完整的 emoji:', renderLine\\(emoji\\)\\)\n\n// 模拟截断 - emoji的UTF-8字节是 \\\\xF0\\\\x9F\\\\x8E\\\\x89\nconst partial = '\\\\xF0\\\\x9F' // 不完整的emoji\nconsole.log\\('截断的 emoji:', renderLine\\(partial\\)\\)\n\")",
21
+ "Bash(node -e \"\nconst isIncompleteUTF8 = \\(str\\) => {\n if \\(!str || str.length === 0\\) return false\n const lastChar = str.charCodeAt\\(str.length - 1\\)\n if \\(lastChar >= 0x80 && lastChar < 0xC0\\) return true\n return false\n}\n\n// 测试各种emoji\nconst tests = ['🎉', '📁', '⚡', '🛠️', '🔑', '💾', '📝', '⏰', '🔌', '📋']\ntests.forEach\\(e => {\n console.log\\(e, '完整:', !isIncompleteUTF8\\(e\\)\\)\n}\\)\n\n// 测试被截断的emoji \\(只保留第一字节\\)\nconst broken = '🎉'.slice\\(0, 1\\)\nconsole.log\\('截断emoji:', broken, '检测:', isIncompleteUTF8\\(broken\\)\\)\n\")",
22
+ "Bash(node -e \"\nconst { renderLine } = require\\('./cli/src/utils/markdown'\\)\nconsole.log\\('测试:', renderLine\\('📁 文件操作:读取、创建'\\)\\)\n\")",
23
+ "Bash(node -e \"\nconst hasIncompleteSurrogate = \\(str\\) => {\n if \\(!str || str.length === 0\\) return false\n const lastChar = str.charCodeAt\\(str.length - 1\\)\n console.log\\('检查:', str, 'lastChar:', lastChar.toString\\(16\\), '范围:', \\(lastChar >= 0xD800 && lastChar <= 0xDBFF\\)\\)\n return lastChar >= 0xD800 && lastChar <= 0xDBFF\n}\n\nconst chunk1 = '\\\\xD83C'\nconsole.log\\('结果:', hasIncompleteSurrogate\\(chunk1\\)\\)\n\")",
24
+ "Bash(cd D:/Code/vb-agent && node test-stream-emoji.js 2>&1)",
25
+ "Read(//d/Date/20260321/app/**)",
26
+ "Bash(node -e \":*)",
27
+ "Bash(node -e \"\nconst { loadAgentConfig } = require\\('./plugins/default-plugins'\\);\nconst config = loadAgentConfig\\('D:/Date/20260321/app/.agent'\\);\nconsole.log\\('skillsDirs:', config.skillsDirs\\);\n\")",
28
+ "Bash(cd D:/Code/vb-agent && pnpm install --shamefully-hoist 2>&1 | head -20)",
29
+ "Bash(cd D:/Code/vb-agent && rm -rf node_modules && pnpm install)",
30
+ "Bash(cd D:/Code/vb-agent && timeout 8 node test-tg.js 2>&1 || true)",
31
+ "Bash(cd D:/Code/vb-agent && timeout 10 node test-tg.js 2>&1 || true)",
32
+ "Bash(find /d/Code/vb-agent -name \"*email*\" -type f 2>/dev/null | grep -v node_modules | grep -v .git)",
33
+ "Bash(find /d/Code/vb-agent -maxdepth 2 -name \"*.md\" -type f 2>/dev/null | grep -v node_modules)",
34
+ "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && echo \"Syntax OK\")",
35
+ "Bash(node -c plugins/install-plugin.js && echo \"Syntax OK\")",
36
+ "Bash(node -c cli/src/ui/chat-ui.js && echo \"Syntax OK\")",
37
+ "Bash(node -c plugins/default-plugins.js && echo \"Syntax OK\")",
38
+ "Bash(node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
39
+ "Bash(node -c plugins/telegram-plugin.js && node -c plugins/scheduler-plugin.js && echo \"Syntax OK\")",
40
+ "Bash(node -c plugins/telegram-plugin.js && echo \"Syntax OK\")",
41
+ "WebSearch",
42
+ "Bash(npm ls:*)",
43
+ "Bash(cd node_modules/@chnak/weixin-bot && npm run build 2>&1)",
44
+ "Bash(cd node_modules/@chnak/weixin-bot && npx tsc 2>&1)",
45
+ "Bash(npm install:*)",
46
+ "Bash(cd node_modules/@chnak/weixin-bot && npx typescript --version && npx tsc 2>&1)",
47
+ "Bash(npx tsc:*)",
48
+ "Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js 2>&1)",
49
+ "Bash(node --check /d/Code/vb-agent/plugins/telegram-plugin.js && node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
50
+ "Bash(node --check /d/Code/vb-agent/cli/src/ui/chat-ui.js && echo \"OK\")",
51
+ "Bash(npm uninstall:*)",
52
+ "Bash(rm -rf /tmp/weixin-bot && git clone https://github.com/chnak/weixin-bot.git /tmp/weixin-bot 2>&1)",
53
+ "Bash(mkdir -p node_modules/@chnak/weixin-bot && cp -r /tmp/weixin-bot/nodejs/* node_modules/@chnak/weixin-bot/ && cd node_modules/@chnak/weixin-bot && npm install 2>&1)",
54
+ "Bash(node --check /d/Code/vb-agent/plugins/weixin-plugin.js && echo \"OK\")",
55
+ "Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', Object.keys\\(m\\)\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\")",
56
+ "Bash(npm run:*)",
57
+ "Bash(node -e \"const { WeixinBot } = require\\('@chnak/weixin-bot'\\); console.log\\(typeof WeixinBot\\)\" 2>&1)",
58
+ "Bash(node -e \"import\\('@chnak/weixin-bot'\\).then\\(m => console.log\\('OK:', typeof m.WeixinBot\\)\\).catch\\(e => console.error\\('Error:', e.message\\)\\)\" 2>&1)",
59
+ "Bash(ls -la D:/code/vb-agent/cli/bin/ && cat D:/code/vb-agent/cli/bin/*.js 2>/dev/null | head -50)",
60
+ "Bash(npm config:*)",
61
+ "Bash(node -c plugins/subagent-plugin.js 2>&1)",
62
+ "Bash(node -c plugins/default-plugins.js 2>&1)",
63
+ "Bash(node -c plugins/default-plugins.js && node -c src/core/plugin-manager.js && node -c plugins/tools-plugin.js 2>&1)",
64
+ "Bash(node -c src/core/plugin-base.js && node -c src/core/plugin-manager.js 2>&1)",
65
+ "Bash(node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
66
+ "Bash(node -c src/core/plugin-manager.js 2>&1)",
67
+ "Bash(node -c src/core/plugin-manager.js && node -c src/core/plugin-base.js && node -c plugins/default-plugins.js && node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
68
+ "Bash(node -e \"require\\('dotenv'\\).config\\(\\); console.log\\('PROVIDER:', process.env.FOLIKO_PROVIDER\\); console.log\\('MODEL:', process.env.FOLIKO_MODEL\\); console.log\\('KEY:', process.env.DEEPSEEK_API_KEY ? 'set' : 'not set'\\)\" 2>&1)",
69
+ "Bash(node cli/src/index.js chat 2>&1 | head -30)",
70
+ "Bash(node -e 'const { DEFAULT_PROVIDERS } = require\\(\"./src/core/provider\"\\); console.log\\(DEFAULT_PROVIDERS\\);')",
71
+ "Bash(node -e \"const dotenv = require\\('dotenv'\\); const result = dotenv.config\\(\\); console.log\\('Result:', result\\); console.log\\('PROVIDER after dotenv:', process.env.FOLIKO_PROVIDER\\);\" 2>&1)",
72
+ "Bash(node -c plugins/email.js && node -c plugins/default-plugins.js 2>&1)",
73
+ "Bash(node -c plugins/email.js 2>&1)",
74
+ "Bash(npm list:*)",
75
+ "Bash(node -e \"const {EmailPlugin} = require\\('./plugins/email'\\); const p = new EmailPlugin\\(\\); console.log\\('email plugin loaded ok'\\); console.log\\('enabled:', p.enabled\\); console.log\\('version:', p.version\\);\" 2>&1)",
76
+ "Bash(cd D:/Code/vb-agent && node -e \"console.log\\('IMAP_HOST:', process.env.IMAP_HOST\\); console.log\\('IMAP_USER:', process.env.IMAP_USER\\); console.log\\('IMAP_PORT:', process.env.IMAP_PORT\\);\")",
77
+ "Bash(node -e \"const sdk = require\\('@larksuiteoapi/node-sdk'\\); console.log\\(Object.keys\\(sdk\\)\\);\")",
78
+ "Bash(node -e \"const { WSClient } = require\\('@larksuiteoapi/node-sdk'\\); const sdk = new WSClient\\({}\\); console.log\\(Object.getOwnPropertyNames\\(Object.getPrototypeOf\\(sdk\\)\\)\\);\")"
79
+ ]
80
+ }
81
+ }
package/package.json CHANGED
@@ -1,43 +1,44 @@
1
- {
2
- "name": "foliko",
3
- "version": "1.0.30",
4
- "description": "简约的插件化 Agent 框架",
5
- "main": "src/index.js",
6
- "bin": {
7
- "foliko": "./cli/bin/foliko.js"
8
- },
9
- "scripts": {
10
- "start": "node examples/basic.js",
11
- "chat": "node cli/bin/foliko.js chat",
12
- "test": "echo \"No tests yet\""
13
- },
14
- "keywords": [
15
- "agent",
16
- "ai",
17
- "framework",
18
- "plugin"
19
- ],
20
- "author": "",
21
- "license": "MIT",
22
- "dependencies": {
23
- "@ai-sdk/anthropic": "^3.0.58",
24
- "@ai-sdk/mcp": "^1.0.25",
25
- "@ai-sdk/openai": "^3.0.41",
26
- "@ai-sdk/openai-compatible": "^2.0.35",
27
- "@anthropic-ai/sdk": "^0.39.0",
28
- "@chnak/weixin-bot": "^1.2.0",
29
- "@modelcontextprotocol/sdk": "^1.27.1",
30
- "ai": "^6.0.116",
31
- "dotenv": "^17.3.1",
32
- "imap": "^0.8.19",
33
- "imap-mkl": "^1.0.2",
34
- "mailparser": "^3.7.2",
35
- "marked": "^11.2.0",
36
- "marked-terminal": "6",
37
- "node-cron": "^4.2.1",
38
- "node-telegram-bot-api": "^0.67.0",
39
- "nodemailer": "^6.10.0",
40
- "qrcode-terminal": "^0.12.0",
41
- "zod": "^3.24.0"
42
- }
43
- }
1
+ {
2
+ "name": "foliko",
3
+ "version": "1.0.32",
4
+ "description": "简约的插件化 Agent 框架",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "foliko": "./cli/bin/foliko.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node examples/basic.js",
11
+ "chat": "node cli/bin/foliko.js chat",
12
+ "test": "echo \"No tests yet\""
13
+ },
14
+ "keywords": [
15
+ "agent",
16
+ "ai",
17
+ "framework",
18
+ "plugin"
19
+ ],
20
+ "author": "",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@ai-sdk/anthropic": "^3.0.58",
24
+ "@ai-sdk/mcp": "^1.0.25",
25
+ "@ai-sdk/openai": "^3.0.41",
26
+ "@ai-sdk/openai-compatible": "^2.0.35",
27
+ "@anthropic-ai/sdk": "^0.39.0",
28
+ "@chnak/weixin-bot": "^1.2.0",
29
+ "@larksuiteoapi/node-sdk": "^1.59.0",
30
+ "@modelcontextprotocol/sdk": "^1.27.1",
31
+ "ai": "^6.0.116",
32
+ "dotenv": "^17.3.1",
33
+ "imap": "^0.8.19",
34
+ "imap-mkl": "^1.0.2",
35
+ "mailparser": "^3.7.2",
36
+ "marked": "^11.2.0",
37
+ "marked-terminal": "6",
38
+ "node-cron": "^4.2.1",
39
+ "node-telegram-bot-api": "^0.67.0",
40
+ "nodemailer": "^6.10.0",
41
+ "qrcode-terminal": "^0.12.0",
42
+ "zod": "^3.24.0"
43
+ }
44
+ }
@@ -443,7 +443,22 @@ async function bootstrapDefaults(framework, config = {}) {
443
443
  }
444
444
  }
445
445
 
446
- // 12.9 SubAgent 管理器
446
+ // 12.9 Feishu 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
447
+ if (shouldLoad('feishu')) {
448
+ try {
449
+ const { Plugin } = require('../src/core/plugin-base')
450
+ const createFeishuPlugin = require('./feishu-plugin')
451
+ const FeishuPlugin = createFeishuPlugin(Plugin)
452
+ const feishuConfig = {
453
+ allowedUsers: agentConfig.feishu?.allowedUsers || []
454
+ }
455
+ await framework.loadPlugin(new FeishuPlugin(feishuConfig))
456
+ } catch (err) {
457
+ console.warn('[Bootstrap] Feishu Plugin not available:', err.message)
458
+ }
459
+ }
460
+
461
+ // 12.10 SubAgent 管理器
447
462
  if (shouldLoad('subagent-manager')) {
448
463
  try {
449
464
  const { SubAgentManagerPlugin } = require('./subagent-plugin')
package/plugins/email.js CHANGED
@@ -15,16 +15,6 @@ class EmailPlugin extends Plugin {
15
15
  this.priority = 10
16
16
  // 默认不启用,需要在 plugins.json 中设置 enabled: true
17
17
  this.enabled = false
18
- // IMAP ID 信息
19
- this.config = {
20
- ...config,
21
- clientId: config.clientId || {
22
- name: 'FolikoAgent',
23
- version: '1.0.0',
24
- vendor: 'Foliko',
25
- supportEmail: config.imap?.user || 'unknown@example.com'
26
- }
27
- }
28
18
  }
29
19
 
30
20
  install(framework) {
@@ -105,42 +95,22 @@ class EmailPlugin extends Plugin {
105
95
  // 配置邮箱连接
106
96
  this._framework.registerTool({
107
97
  name: 'email_configure',
108
- description: '配置邮箱连接参数',
109
- inputSchema: z.object({
110
- smtp_host: z.string().optional().describe('SMTP服务器地址'),
111
- smtp_port: z.number().optional().describe('SMTP端口'),
112
- smtp_secure: z.boolean().optional().describe('是否使用SSL/TLS'),
113
- smtp_user: z.string().optional().describe('SMTP用户名'),
114
- smtp_pass: z.string().optional().describe('SMTP密码'),
115
- imap_host: z.string().optional().describe('IMAP服务器地址'),
116
- imap_port: z.number().optional().describe('IMAP端口'),
117
- imap_user: z.string().optional().describe('IMAP用户名'),
118
- imap_pass: z.string().optional().describe('IMAP密码'),
119
- from_email: z.string().optional().describe('默认发件人地址'),
120
- client_id_name: z.string().optional().describe('IMAP客户端名称'),
121
- client_id_version: z.string().optional().describe('IMAP客户端版本'),
122
- client_id_vendor: z.string().optional().describe('IMAP客户端厂商'),
123
- client_id_support_email: z.string().optional().describe('IMAP客户端支持邮箱')
124
- }),
125
- execute: async (args) => {
126
- // 处理 clientId 字段
127
- if (args.client_id_name || args.client_id_version || args.client_id_vendor || args.client_id_support_email) {
128
- this.config.clientId = {
129
- name: args.client_id_name || this.config.clientId?.name || 'FolikoAgent',
130
- version: args.client_id_version || this.config.clientId?.version || '1.0.0',
131
- vendor: args.client_id_vendor || this.config.clientId?.vendor || 'Foliko',
132
- supportEmail: args.client_id_support_email || this.config.clientId?.supportEmail
133
- }
134
- delete args.client_id_name
135
- delete args.client_id_version
136
- delete args.client_id_vendor
137
- delete args.client_id_support_email
138
- }
139
- Object.assign(this.config, args)
98
+ description: '查看邮箱配置(实际配置通过环境变量设置)',
99
+ inputSchema: z.object({}),
100
+ execute: async () => {
140
101
  return {
141
102
  success: true,
142
- message: '邮箱配置已更新',
143
- config: this.config
103
+ message: '邮箱配置通过环境变量设置',
104
+ config: {
105
+ smtp_host: process.env.SMTP_HOST || 'smtp.gmail.com',
106
+ smtp_port: process.env.SMTP_PORT || 587,
107
+ smtp_secure: process.env.SMTP_SECURE || 'false',
108
+ imap_host: process.env.IMAP_HOST || 'imap.gmail.com',
109
+ imap_port: process.env.IMAP_PORT || 993,
110
+ from_email: process.env.FROM_EMAIL || '(未设置)',
111
+ client_name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
112
+ client_version: process.env.IMAP_CLIENT_VERSION || '1.0.0'
113
+ }
144
114
  }
145
115
  }
146
116
  })
@@ -151,19 +121,19 @@ class EmailPlugin extends Plugin {
151
121
  const nodemailer = require('nodemailer')
152
122
 
153
123
  const smtpConfig = {
154
- host: this.config.smtp_host || process.env.SMTP_HOST || 'smtp.gmail.com',
155
- port: this.config.smtp_port || process.env.SMTP_PORT || 587,
156
- secure: (this.config.smtp_secure || process.env.SMTP_SECURE || 'false') === 'true',
124
+ host: process.env.SMTP_HOST || 'smtp.gmail.com',
125
+ port: parseInt(process.env.SMTP_PORT) || 587,
126
+ secure: process.env.SMTP_SECURE === 'true',
157
127
  auth: {
158
- user: this.config.smtp_user || process.env.SMTP_USER,
159
- pass: this.config.smtp_pass || process.env.SMTP_PASS
128
+ user: process.env.SMTP_USER,
129
+ pass: process.env.SMTP_PASS
160
130
  }
161
131
  }
162
132
 
163
133
  const transporter = nodemailer.createTransport(smtpConfig)
164
134
 
165
135
  const mailOptions = {
166
- from: this.config.from_email || process.env.FROM_EMAIL || smtpConfig.auth.user,
136
+ from: process.env.FROM_EMAIL || smtpConfig.auth.user,
167
137
  to: args.to,
168
138
  subject: args.subject,
169
139
  text: args.isHtml ? undefined : args.body,
@@ -195,16 +165,16 @@ class EmailPlugin extends Plugin {
195
165
  const { simpleParser } = require('mailparser')
196
166
 
197
167
  const imapConfig = {
198
- user: args.user || this.config.imap_user || process.env.IMAP_USER,
199
- password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
200
- host: args.host || this.config.imap_host || process.env.IMAP_HOST,
201
- port: args.port || this.config.imap_port || 993,
168
+ user: args.user || process.env.IMAP_USER,
169
+ password: args.password || process.env.IMAP_PASS,
170
+ host: args.host || process.env.IMAP_HOST,
171
+ port: args.port || parseInt(process.env.IMAP_PORT) || 993,
202
172
  tls: true,
203
173
  tlsOptions: { rejectUnauthorized: false },
204
174
  id: {
205
- name: this.config.clientId?.name || 'FolikoAgent',
206
- version: this.config.clientId?.version || '1.0.0',
207
- vendor: this.config.clientId?.vendor || 'Foliko'
175
+ name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
176
+ version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
177
+ vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko'
208
178
  }
209
179
  }
210
180
 
@@ -233,17 +203,17 @@ class EmailPlugin extends Plugin {
233
203
  const Imap = require('imap-mkl')
234
204
 
235
205
  const imapConfig = {
236
- user: args.user || this.config.imap_user || process.env.IMAP_USER,
237
- password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
238
- host: args.host || this.config.imap_host || process.env.IMAP_HOST,
239
- port: args.port || this.config.imap_port || 993,
206
+ user: args.user || process.env.IMAP_USER,
207
+ password: args.password || process.env.IMAP_PASS,
208
+ host: args.host || process.env.IMAP_HOST,
209
+ port: args.port || parseInt(process.env.IMAP_PORT) || 993,
240
210
  tls: true,
241
211
  tlsOptions: { rejectUnauthorized: false },
242
212
  id: {
243
- name: this.config.clientId?.name || 'FolikoAgent',
244
- version: this.config.clientId?.version || '1.0.0',
245
- vendor: this.config.clientId?.vendor || 'Foliko',
246
- 'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
213
+ name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
214
+ version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
215
+ vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
216
+ 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
247
217
  }
248
218
  }
249
219
 
@@ -268,17 +238,17 @@ class EmailPlugin extends Plugin {
268
238
  const Imap = require('imap-mkl')
269
239
 
270
240
  const imapConfig = {
271
- user: args.user || this.config.imap_user || process.env.IMAP_USER,
272
- password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
273
- host: args.host || this.config.imap_host || process.env.IMAP_HOST,
274
- port: args.port || this.config.imap_port || 993,
241
+ user: args.user || process.env.IMAP_USER,
242
+ password: args.password || process.env.IMAP_PASS,
243
+ host: args.host || process.env.IMAP_HOST,
244
+ port: args.port || parseInt(process.env.IMAP_PORT) || 993,
275
245
  tls: true,
276
246
  tlsOptions: { rejectUnauthorized: false },
277
247
  id: {
278
- name: this.config.clientId?.name || 'FolikoAgent',
279
- version: this.config.clientId?.version || '1.0.0',
280
- vendor: this.config.clientId?.vendor || 'Foliko',
281
- 'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
248
+ name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
249
+ version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
250
+ vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
251
+ 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
282
252
  }
283
253
  }
284
254
 
@@ -314,7 +284,9 @@ class EmailPlugin extends Plugin {
314
284
  return reject(err)
315
285
  }
316
286
 
317
- let searchFilter = searchCriteria ? [searchCriteria] : ['ALL']
287
+ let searchFilter = searchCriteria
288
+ ? searchCriteria.split(' ').filter(Boolean)
289
+ : ['ALL']
318
290
  if (unreadOnly) {
319
291
  searchFilter = ['UNSEEN']
320
292
  }
@@ -334,24 +306,25 @@ class EmailPlugin extends Plugin {
334
306
  const f = imap.fetch(fetchIds, { bodies: '' })
335
307
 
336
308
  f.on('message', (msg) => {
337
- const email = {}
309
+ let email = {}
310
+ let bodyParsed = false
338
311
 
339
312
  msg.on('body', (stream) => {
340
- simpleParser(stream, (err, mail) => {
341
- if (err) {
342
- email.error = err.message
343
- } else {
344
- email.subject = mail.subject
345
- email.from = mail.from?.text || ''
346
- email.to = mail.to?.text || ''
347
- email.date = mail.date?.toISOString() || ''
348
- email.text = mail.text
349
- email.html = mail.html
350
- email.attachments = mail.attachments?.map(a => ({
351
- filename: a.filename,
352
- contentType: a.contentType
353
- })) || []
354
- }
313
+ simpleParser(stream).then(mail => {
314
+ email.subject = mail.subject
315
+ email.from = mail.from?.text || ''
316
+ email.to = mail.to?.text || ''
317
+ email.date = mail.date?.toISOString() || ''
318
+ email.text = mail.text || mail.textAsHtml || ''
319
+ email.html = mail.html
320
+ email.attachments = mail.attachments?.map(a => ({
321
+ filename: a.filename,
322
+ contentType: a.contentType
323
+ })) || []
324
+ bodyParsed = true
325
+ }).catch(err => {
326
+ email.error = err.message
327
+ bodyParsed = true
355
328
  })
356
329
  })
357
330
 
@@ -362,7 +335,15 @@ class EmailPlugin extends Plugin {
362
335
  })
363
336
 
364
337
  msg.on('end', () => {
365
- emails.push(email)
338
+ // 等待 body 解析完成后再加入列表
339
+ const checkDone = () => {
340
+ if (bodyParsed) {
341
+ emails.push(email)
342
+ } else {
343
+ setTimeout(checkDone, 10)
344
+ }
345
+ }
346
+ checkDone()
366
347
  })
367
348
  })
368
349
 
@@ -372,8 +353,16 @@ class EmailPlugin extends Plugin {
372
353
  })
373
354
 
374
355
  f.on('end', () => {
375
- cleanup()
376
- resolve(emails)
356
+ // 等待所有邮件解析完成
357
+ const waitForAll = () => {
358
+ if (emails.length === fetchIds.length) {
359
+ cleanup()
360
+ resolve(emails)
361
+ } else {
362
+ setTimeout(waitForAll, 10)
363
+ }
364
+ }
365
+ waitForAll()
377
366
  })
378
367
  })
379
368
  })
@@ -0,0 +1,612 @@
1
+ /**
2
+ * 飞书插件
3
+ * 使用 @larksuiteoapi/node-sdk 的 WebSocket 长连接模式
4
+ *
5
+ * 配置:
6
+ * - appId: 飞书应用 ID (默认从 FEISHU_APP_ID 环境变量获取)
7
+ * - appSecret: 飞书应用密钥 (默认从 FEISHU_APP_SECRET 环境变量获取)
8
+ * - allowedUsers: 允许的用户 openId 数组,为空则允许所有用户
9
+ */
10
+
11
+ const { Plugin } = require('../src/core/plugin-base')
12
+
13
+ module.exports = function(Plugin) {
14
+ return class FeishuPlugin extends Plugin {
15
+ constructor(config = {}) {
16
+ super()
17
+ this.name = 'feishu'
18
+ this.version = '1.0.0'
19
+ this.description = '飞书对话插件,使用 WebSocket 长连接接收消息'
20
+ this.priority = 80
21
+ // 默认不启用,需要在 plugins.json 中设置 enabled: true
22
+ this.enabled = false
23
+
24
+ this.config = {
25
+ appId: config.appId || process.env.FEISHU_APP_ID,
26
+ appSecret: config.appSecret || process.env.FEISHU_APP_SECRET,
27
+ allowedUsers: config.allowedUsers || [],
28
+ systemPrompt: '你是一个飞书助手。回复内容不要使用markdown格式文本。'
29
+ }
30
+
31
+ this._framework = null
32
+ this._client = null
33
+ this._wsClient = null
34
+ this._sessionPlugin = null
35
+ this._sessionDeleteHandler = null
36
+ this._sessionAgents = new Map() // openId -> Agent
37
+ this._initialized = false
38
+ this._processedMessages = new Set() // 消息去重
39
+ this._messageCleanupInterval = null
40
+ this._processingLock = new Map() // openId -> boolean (是否正在处理)
41
+ }
42
+
43
+ install(framework) {
44
+ this._framework = framework
45
+ return this
46
+ }
47
+
48
+ start(framework) {
49
+ // 防止重复初始化
50
+ if (this._initialized) return this
51
+ this._initialized = true
52
+
53
+ if (!this.config.appId || !this.config.appSecret) {
54
+ console.warn('[Feishu] No appId or appSecret. Set FEISHU_APP_ID and FEISHU_APP_SECRET env vars.')
55
+ return this
56
+ }
57
+
58
+ // 获取 SessionPlugin 引用
59
+ this._sessionPlugin = framework.pluginManager.get('session')
60
+
61
+ // 监听 SessionPlugin 的会话删除事件
62
+ if (this._sessionPlugin) {
63
+ this._sessionDeleteHandler = (sessionId) => {
64
+ // 只处理 feishu 会话的删除
65
+ if (sessionId.startsWith('feishu_')) {
66
+ console.log(`[Feishu] Session deleted: ${sessionId}`)
67
+ }
68
+ }
69
+ this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
70
+ }
71
+
72
+ this._initClient()
73
+ return this
74
+ }
75
+
76
+ /**
77
+ * 初始化飞书 WebSocket 客户端
78
+ */
79
+ _initClient() {
80
+ try {
81
+ const { Client, WSClient, EventDispatcher } = require('@larksuiteoapi/node-sdk')
82
+
83
+ this._client = new Client({
84
+ appId: this.config.appId,
85
+ appSecret: this.config.appSecret,
86
+ logger: console
87
+ })
88
+
89
+ this._wsClient = new WSClient({
90
+ appId: this.config.appId,
91
+ appSecret: this.config.appSecret,
92
+ logger: console
93
+ })
94
+
95
+ // 创建事件分发器并注册处理器
96
+ const eventDispatcher = new EventDispatcher({}).register({
97
+ 'im.message.receive_v1': async (data) => {
98
+ await this._handleMessage(data)
99
+ }
100
+ })
101
+
102
+ // 启动 WebSocket 连接
103
+ this._wsClient.start({ eventDispatcher })
104
+
105
+ // 启动消息去重清理定时器(每分钟清理一次)
106
+ this._messageCleanupInterval = setInterval(() => {
107
+ this._processedMessages.clear()
108
+ }, 60000)
109
+
110
+ // 监听定时提醒事件
111
+ if (this._framework) {
112
+ this._framework.on('scheduler:reminder', async (data) => {
113
+ console.log('[Feishu] Received scheduler reminder:', data)
114
+ await this._handleScheduledReminder(data)
115
+ })
116
+ }
117
+
118
+ console.log('[Feishu] WebSocket client started')
119
+ } catch (err) {
120
+ console.error('[Feishu] Failed to initialize client:', err.message, err.stack)
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 处理飞书消息
126
+ */
127
+ async _handleMessage(data) {
128
+ try {
129
+ // 解析消息数据 - SDK 事件格式
130
+ // data 结构: { message: { chat_id, content, sender: { sender_id: { open_id }, ... }, message_type } }
131
+ if (!data || !data.message) {
132
+ console.log('[Feishu] Invalid message data:', JSON.stringify(data).substring(0, 500))
133
+ return
134
+ }
135
+
136
+ const message = data.message
137
+ const chatId = message.chat_id
138
+ const messageId = message.message_id
139
+ // 飞书消息结构: sender.sender_id.open_id 或 sender.open_id
140
+ // 注意: 群组消息可能没有 sender 字段,此时使用 chat_id 作为会话标识
141
+ const openId = message.sender?.sender_id?.open_id || message.sender?.open_id || message.open_id || chatId
142
+ const messageType = message.message_type
143
+ const content = message.content ? JSON.parse(message.content) : {}
144
+
145
+ // 消息去重
146
+ if (messageId && this._processedMessages.has(messageId)) {
147
+ console.log(`[Feishu] Duplicate message ignored: ${messageId}`)
148
+ return
149
+ }
150
+ if (messageId) {
151
+ this._processedMessages.add(messageId)
152
+ }
153
+
154
+ // 忽略非文本消息
155
+ if (messageType !== 'text') {
156
+ console.log(`[Feishu] Unsupported message type: ${messageType}`)
157
+ return
158
+ }
159
+
160
+ const text = content.text?.trim()
161
+ if (!text) {
162
+ console.log('[Feishu] Empty text message')
163
+ return
164
+ }
165
+
166
+ // 权限检查
167
+ if (!this._checkPermission(openId)) {
168
+ console.log(`[Feishu] User ${openId} not allowed`)
169
+ return
170
+ }
171
+
172
+ // 获取历史消息数量
173
+ let messageCount = 0
174
+ if (this._sessionPlugin) {
175
+ const session = this._sessionPlugin.getSession(`feishu_${openId}`)
176
+ messageCount = session?.messages?.length || 0
177
+ }
178
+
179
+ console.log(`[Feishu] #${messageCount + 1} | Chat: ${chatId} | User: ${openId}`)
180
+ console.log(`[Feishu] Content: ${text}`)
181
+
182
+ // 命令处理
183
+ if (text.startsWith('/')) {
184
+ await this._handleCommand(openId, text, message)
185
+ return
186
+ }
187
+
188
+ // 检查是否正在处理,防止并发
189
+ if (this._processingLock.get(openId)) {
190
+ console.log(`[Feishu] Session ${openId} is busy, ignoring message`)
191
+ return
192
+ }
193
+ this._processingLock.set(openId, true)
194
+
195
+ try {
196
+ await this._processChat(openId, text, message)
197
+ } finally {
198
+ this._processingLock.set(openId, false)
199
+ }
200
+
201
+ } catch (err) {
202
+ console.error('[Feishu] Error handling message:', err)
203
+ }
204
+ }
205
+
206
+ /**
207
+ * 处理命令
208
+ */
209
+ async _handleCommand(openId, text, originalMsg) {
210
+ const parts = text.split(' ')
211
+ const command = parts[0].substring(1)
212
+ const args = parts.slice(1).join(' ')
213
+
214
+ switch (command.toLowerCase()) {
215
+ case 'start':
216
+ case 'help':
217
+ await this._sendMessage(openId,
218
+ '👋 欢迎使用 AI 助手!\n\n' +
219
+ '直接发送消息即可与我对话。\n\n' +
220
+ '可用命令:\n' +
221
+ '/start - 显示帮助\n' +
222
+ '/clear - 清除对话历史\n' +
223
+ '/history - 查看历史消息数',
224
+ originalMsg)
225
+ break
226
+
227
+ case 'clear':
228
+ this._clearSession(openId)
229
+ await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
230
+ break
231
+
232
+ case 'history':
233
+ if (this._sessionPlugin) {
234
+ const sessionId = `feishu_${openId}`
235
+ const session = this._sessionPlugin.getSession(sessionId)
236
+ const allSessions = this._sessionPlugin.listSessions()
237
+ const feishuSessionCount = allSessions.filter(s => s.id.startsWith('feishu_')).length
238
+ await this._sendMessage(openId,
239
+ `📊 当前状态\n\n会话数:${feishuSessionCount}\n` +
240
+ `历史消息:${session?.messages?.length || 0}\n` +
241
+ `最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
242
+ originalMsg)
243
+ } else {
244
+ await this._sendMessage(openId, '❌ 会话服务不可用', originalMsg)
245
+ }
246
+ break
247
+
248
+ default:
249
+ // 未识别命令,作为普通消息处理
250
+ await this._processChat(openId, text, originalMsg)
251
+ }
252
+ }
253
+
254
+ /**
255
+ * 获取或创建会话 Agent
256
+ * 使用 SessionPlugin 统一管理会话历史
257
+ */
258
+ _getSessionAgent(openId) {
259
+ // 检查缓存
260
+ if (this._sessionAgents.has(openId)) {
261
+ const agent = this._sessionAgents.get(openId)
262
+ console.log('[Feishu] Reusing cached session agent for openId:', openId)
263
+ return { agent, sessionId: `feishu_${openId}` }
264
+ }
265
+
266
+ // 创建新 agent
267
+ const agent = this._framework.createSessionAgent(`feishu_${openId}`, {
268
+ systemPrompt: this.config.systemPrompt
269
+ })
270
+ this._sessionAgents.set(openId, agent)
271
+ console.log('[Feishu] Created new session agent for openId:', openId)
272
+
273
+ // 使用 SessionPlugin 管理会话历史
274
+ if (this._sessionPlugin) {
275
+ const sessionId = `feishu_${openId}`
276
+ this._sessionPlugin.getOrCreateSession(sessionId, {
277
+ metadata: { platform: 'feishu', openId }
278
+ })
279
+ }
280
+
281
+ console.log(`[Feishu] Session ready for openId: ${openId}`)
282
+ return { agent, sessionId: `feishu_${openId}` }
283
+ }
284
+
285
+ /**
286
+ * 处理定时提醒
287
+ */
288
+ async _handleScheduledReminder(data) {
289
+ const { taskName, message, sessionId } = data
290
+
291
+ // 构建提醒消息
292
+ const reminderText = `🔔 [${taskName}]\n\n${message}`
293
+
294
+ // 如果有 sessionId 是 feishu 类型的,发送到对应用户
295
+ if (sessionId && sessionId.startsWith('feishu_')) {
296
+ const openId = sessionId.replace('feishu_', '')
297
+ try {
298
+ await this._sendTextMessage(openId, reminderText)
299
+ console.log(`[Feishu] Reminder sent to ${openId}`)
300
+ } catch (err) {
301
+ console.error(`[Feishu] Failed to send reminder:`, err.message)
302
+ }
303
+ return
304
+ }
305
+
306
+ // 其他情况,发送到最近的 Feishu 会话
307
+ if (this._sessionPlugin) {
308
+ const allSessions = this._sessionPlugin.listSessions()
309
+ const feishuSessions = allSessions
310
+ .filter(s => s.id.startsWith('feishu_'))
311
+ .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
312
+
313
+ if (feishuSessions.length > 0) {
314
+ const openId = feishuSessions[0].id.replace('feishu_', '')
315
+ try {
316
+ await this._sendTextMessage(openId, reminderText)
317
+ console.log(`[Feishu] Reminder sent to ${openId}`)
318
+ } catch (err) {
319
+ console.error(`[Feishu] Failed to send reminder to ${openId}:`, err.message)
320
+ }
321
+ } else {
322
+ console.log('[Feishu] No active Feishu sessions to send reminder')
323
+ }
324
+ } else {
325
+ console.log('[Feishu] No active Feishu sessions to send reminder')
326
+ }
327
+ }
328
+
329
+ /**
330
+ * 发送纯文本消息(内部使用,不经过 markdown 处理)
331
+ */
332
+ async _sendTextMessage(openId, text) {
333
+ if (!this._client) return
334
+
335
+ try {
336
+ await this._client.im.message.create({
337
+ params: {
338
+ receive_id_type: 'chat_id'
339
+ },
340
+ data: {
341
+ receive_id: openId,
342
+ content: JSON.stringify({ text: text }),
343
+ msg_type: 'text'
344
+ }
345
+ })
346
+ } catch (err) {
347
+ console.error('[Feishu] Failed to send text message:', err.message)
348
+ throw err
349
+ }
350
+ }
351
+
352
+ /**
353
+ * 处理对话
354
+ */
355
+ async _processChat(openId, text, originalMsg) {
356
+ const sessionInfo = this._getSessionAgent(openId)
357
+ if (!sessionInfo) {
358
+ console.error('[Feishu] No session agent available')
359
+ return
360
+ }
361
+
362
+ const { agent, sessionId } = sessionInfo
363
+
364
+ // 使用 SessionPlugin 添加用户消息到历史
365
+ if (this._sessionPlugin) {
366
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
367
+ }
368
+
369
+ try {
370
+ let fullResponse = ''
371
+
372
+ // 使用流式响应
373
+ for await (const chunk of agent.chatStream(text, {
374
+ sessionId: sessionId
375
+ })) {
376
+ if (chunk.type === 'text' && chunk.text) {
377
+ fullResponse += chunk.text
378
+ }
379
+ }
380
+
381
+ // 保存助手回复到历史(使用 SessionPlugin)
382
+ if (this._sessionPlugin) {
383
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
384
+ }
385
+
386
+ // 发送回复
387
+ if (fullResponse) {
388
+ await this._sendMessage(openId, fullResponse, originalMsg)
389
+ console.log(`[Feishu] Reply sent (${fullResponse.length} characters)`)
390
+ } else {
391
+ await this._sendMessage(openId, '抱歉,我没有收到有效的回复。', originalMsg)
392
+ }
393
+
394
+ } catch (err) {
395
+ console.error('[Feishu] Chat error:', err)
396
+ await this._sendMessage(openId, `发生错误:${err.message}`, originalMsg)
397
+ }
398
+ }
399
+
400
+ /**
401
+ * 发送消息到飞书
402
+ * 使用 post 富文本类型
403
+ */
404
+ async _sendMessage(openId, text, originalMsg) {
405
+ if (!this._client) return
406
+
407
+ try {
408
+ // 获取用户所在的聊天 ID(回复到原消息所在的聊天)
409
+ const chatId = originalMsg?.chat_id || originalMsg?.message?.chat_id
410
+ if (!chatId) {
411
+ console.error('[Feishu] No chat_id found for reply')
412
+ return
413
+ }
414
+
415
+ // 暂时用 text 类型,等解决 post 权限问题后再切换
416
+ await this._client.im.message.create({
417
+ params: {
418
+ receive_id_type: 'chat_id'
419
+ },
420
+ data: {
421
+ receive_id: chatId,
422
+ content: JSON.stringify({ text: text }),
423
+ msg_type: 'text'
424
+ }
425
+ })
426
+ } catch (err) {
427
+ console.error('[Feishu] Failed to send message:', err.message, err.response?.data)
428
+ }
429
+ }
430
+
431
+ /**
432
+ * 构建飞书富文本内容
433
+ */
434
+ _buildPostContent(text) {
435
+ const lines = text.split('\n')
436
+ const paragraphs = []
437
+
438
+ for (const line of lines) {
439
+ if (line.trim() === '') {
440
+ paragraphs.push([{ tag: 'text', text: ' ' }])
441
+ continue
442
+ }
443
+
444
+ const segments = []
445
+ let remaining = line
446
+
447
+ while (remaining.length > 0) {
448
+ // 匹配加粗 **text**
449
+ const boldMatch = remaining.match(/^\*\*(.+?)\*\*/)
450
+ if (boldMatch) {
451
+ segments.push({ tag: 'text', text: boldMatch[1], bold: true })
452
+ remaining = remaining.slice(boldMatch[0].length)
453
+ continue
454
+ }
455
+
456
+ // 匹配斜体 *text* 或 _text_
457
+ const italicMatch = remaining.match(/^\*([^*]+?)\*|^_([^_]+?)_/)
458
+ if (italicMatch) {
459
+ const italicText = italicMatch[1] !== undefined ? italicMatch[1] : italicMatch[2]
460
+ segments.push({ tag: 'text', text: italicText, italic: true })
461
+ remaining = remaining.slice(italicMatch[0].length)
462
+ continue
463
+ }
464
+
465
+ // 匹配行内代码 `code`
466
+ const codeMatch = remaining.match(/^`([^`]+?)`/)
467
+ if (codeMatch) {
468
+ segments.push({ tag: 'text', text: codeMatch[1], code: true })
469
+ remaining = remaining.slice(codeMatch[0].length)
470
+ continue
471
+ }
472
+
473
+ // 匹配删除线 ~~text~~
474
+ const delMatch = remaining.match(/^~~(.+?)~~/)
475
+ if (delMatch) {
476
+ segments.push({ tag: 'text', text: delMatch[1], strikethrough: true })
477
+ remaining = remaining.slice(delMatch[0].length)
478
+ continue
479
+ }
480
+
481
+ // 匹配链接 [text](url)
482
+ const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/)
483
+ if (linkMatch) {
484
+ segments.push({ tag: 'a', text: linkMatch[1], href: linkMatch[2] })
485
+ remaining = remaining.slice(linkMatch[0].length)
486
+ continue
487
+ }
488
+
489
+ // 普通文本
490
+ const nextSpecial = remaining.search(/(\*|_|`|~~|\[)/)
491
+ if (nextSpecial === -1) {
492
+ segments.push({ tag: 'text', text: remaining })
493
+ break
494
+ } else if (nextSpecial === 0) {
495
+ segments.push({ tag: 'text', text: remaining[0] })
496
+ remaining = remaining.slice(1)
497
+ } else {
498
+ segments.push({ tag: 'text', text: remaining.slice(0, nextSpecial) })
499
+ remaining = remaining.slice(nextSpecial)
500
+ }
501
+ }
502
+
503
+ if (segments.length > 0) {
504
+ paragraphs.push(segments)
505
+ }
506
+ }
507
+
508
+ if (paragraphs.length === 0) {
509
+ paragraphs.push([{ tag: 'text', text: ' ' }])
510
+ }
511
+
512
+ return {
513
+ post: {
514
+ zh_cn: {
515
+ title: '',
516
+ content: paragraphs
517
+ }
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * 权限检查
524
+ */
525
+ _checkPermission(openId) {
526
+ if (!this.config.allowedUsers || this.config.allowedUsers.length === 0) {
527
+ return true
528
+ }
529
+ return this.config.allowedUsers.includes(openId)
530
+ }
531
+
532
+ /**
533
+ * 清除会话
534
+ */
535
+ _clearSession(openId) {
536
+ // 清除 SessionPlugin 中的会话(会触发 session:deleted 事件)
537
+ if (this._sessionPlugin) {
538
+ const sessionId = `feishu_${openId}`
539
+ this._sessionPlugin.deleteSession(sessionId)
540
+ }
541
+ }
542
+
543
+ /**
544
+ * 获取插件状态
545
+ */
546
+ getStatus() {
547
+ // 从 SessionPlugin 获取 Feishu 相关会话
548
+ let sessions = []
549
+ if (this._sessionPlugin) {
550
+ const allSessions = this._sessionPlugin.listSessions()
551
+ sessions = allSessions
552
+ .filter(s => s.id.startsWith('feishu_'))
553
+ .map(s => ({
554
+ openId: s.id.replace('feishu_', ''),
555
+ historyLength: s.messageCount,
556
+ lastActive: s.lastActive
557
+ }))
558
+ }
559
+
560
+ return {
561
+ connected: !!this._wsClient,
562
+ sessionCount: sessions.length,
563
+ sessions,
564
+ config: {
565
+ appId: this.config.appId ? `${this.config.appId.substring(0, 10)}...` : null,
566
+ allowedUsersCount: this.config.allowedUsers.length
567
+ }
568
+ }
569
+ }
570
+
571
+ /**
572
+ * 停止客户端
573
+ */
574
+ stopClient() {
575
+ if (this._wsClient) {
576
+ this._wsClient = null
577
+ this._client = null
578
+ console.log('[Feishu] Client stopped')
579
+ }
580
+ }
581
+
582
+ reload(framework) {
583
+ this._framework = framework
584
+ this._initialized = false
585
+ this.stopClient()
586
+ this.start(framework)
587
+ }
588
+
589
+ uninstall(framework) {
590
+ // 销毁所有 session agents
591
+ for (const agent of this._sessionAgents.values()) {
592
+ agent.destroy()
593
+ }
594
+ this._sessionAgents.clear()
595
+
596
+ // 清理去重定时器
597
+ if (this._messageCleanupInterval) {
598
+ clearInterval(this._messageCleanupInterval)
599
+ this._messageCleanupInterval = null
600
+ }
601
+ this._processedMessages.clear()
602
+
603
+ this.stopClient()
604
+ if (this._sessionPlugin && this._sessionDeleteHandler) {
605
+ this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
606
+ this._sessionDeleteHandler = null
607
+ }
608
+ this._sessionPlugin = null
609
+ this._framework = null
610
+ }
611
+ }
612
+ }
@@ -35,7 +35,7 @@ module.exports = function(Plugin) {
35
35
  allowedChats: config.allowedChats || [],
36
36
  groupMode: config.groupMode || false,
37
37
  prefix: config.prefix || '/',
38
- systemPrompt: config.systemPrompt || '你是一个有帮助的AI助手。'
38
+ systemPrompt: config.systemPrompt || '你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。'
39
39
  }
40
40
 
41
41
  this._framework = null
@@ -24,7 +24,7 @@ module.exports = function(Plugin) {
24
24
  this.config = {
25
25
  forceLogin: config.forceLogin || process.env.WEIXIN_FORCE_LOGIN === 'true',
26
26
  qrcodeTerminal: config.qrcodeTerminal !== false && process.env.WEIXIN_QRCODE_TERMINAL !== 'false',
27
- systemPrompt: config.systemPrompt || '你是一个有帮助的AI助手。回复内容不要使用markdown格式文本',
27
+ systemPrompt: '你是一个微信助手。回复内容不要使用markdown格式文本',
28
28
  allowedUsers: config.allowedUsers || []
29
29
  }
30
30
 
@@ -159,7 +159,9 @@ module.exports = function(Plugin) {
159
159
  }
160
160
 
161
161
  // 创建新 agent
162
- const agent = this._framework.createSessionAgent(`weixin_${userId}`)
162
+ const agent = this._framework.createSessionAgent(`weixin_${userId}`, {
163
+ systemPrompt: this.config.systemPrompt
164
+ })
163
165
  this._sessionAgents.set(userId, agent)
164
166
  console.log('[WeChat] Created new session agent for userId:', userId)
165
167