foliko 1.0.74 → 1.0.76

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 (238) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +29 -0
  39. package/.agent/data/plugins-state.json +255 -0
  40. package/.agent/mcp_config.json +4 -0
  41. package/.agent/mcp_config_updated.json +12 -0
  42. package/.agent/plugins.json +5 -0
  43. package/.agent/rules/GEMINI.md +273 -0
  44. package/.agent/rules/allow-rule.md +77 -0
  45. package/.agent/rules/log-rule.md +83 -0
  46. package/.agent/rules/security-rule.md +93 -0
  47. package/.agent/scripts/auto_preview.py +148 -0
  48. package/.agent/scripts/checklist.py +217 -0
  49. package/.agent/scripts/session_manager.py +120 -0
  50. package/.agent/scripts/verify_all.py +327 -0
  51. package/.agent/skills/api-patterns/SKILL.md +81 -0
  52. package/.agent/skills/api-patterns/api-style.md +42 -0
  53. package/.agent/skills/api-patterns/auth.md +24 -0
  54. package/.agent/skills/api-patterns/documentation.md +26 -0
  55. package/.agent/skills/api-patterns/graphql.md +41 -0
  56. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  57. package/.agent/skills/api-patterns/response.md +37 -0
  58. package/.agent/skills/api-patterns/rest.md +40 -0
  59. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  60. package/.agent/skills/api-patterns/security-testing.md +122 -0
  61. package/.agent/skills/api-patterns/trpc.md +41 -0
  62. package/.agent/skills/api-patterns/versioning.md +22 -0
  63. package/.agent/skills/app-builder/SKILL.md +75 -0
  64. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  65. package/.agent/skills/app-builder/feature-building.md +53 -0
  66. package/.agent/skills/app-builder/project-detection.md +34 -0
  67. package/.agent/skills/app-builder/scaffolding.md +118 -0
  68. package/.agent/skills/app-builder/tech-stack.md +40 -0
  69. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  70. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  71. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  72. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  73. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  74. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  75. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  76. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  77. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  78. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  79. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  80. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  81. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  82. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  83. package/.agent/skills/architecture/SKILL.md +55 -0
  84. package/.agent/skills/architecture/context-discovery.md +43 -0
  85. package/.agent/skills/architecture/examples.md +94 -0
  86. package/.agent/skills/architecture/pattern-selection.md +68 -0
  87. package/.agent/skills/architecture/patterns-reference.md +50 -0
  88. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  89. package/.agent/skills/clean-code/SKILL.md +201 -0
  90. package/.agent/skills/doc.md +177 -0
  91. package/.agent/skills/frontend-design/SKILL.md +418 -0
  92. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  93. package/.agent/skills/frontend-design/color-system.md +311 -0
  94. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  95. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  96. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  97. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  98. package/.agent/skills/frontend-design/typography-system.md +345 -0
  99. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  100. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  101. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  102. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  103. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  104. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  105. package/.agent/workflows/brainstorm.md +113 -0
  106. package/.agent/workflows/create.md +59 -0
  107. package/.agent/workflows/debug.md +103 -0
  108. package/.agent/workflows/deploy.md +176 -0
  109. package/.agent/workflows/enhance.md +63 -0
  110. package/.agent/workflows/orchestrate.md +237 -0
  111. package/.agent/workflows/plan.md +89 -0
  112. package/.agent/workflows/preview.md +81 -0
  113. package/.agent/workflows/simple-test.md +42 -0
  114. package/.agent/workflows/status.md +86 -0
  115. package/.agent/workflows/structured-orchestrate.md +180 -0
  116. package/.agent/workflows/test.md +144 -0
  117. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  118. package/.claude/settings.local.json +11 -1
  119. package/.editorconfig +56 -0
  120. package/.husky/pre-commit +4 -0
  121. package/.lintstagedrc +7 -0
  122. package/.prettierignore +29 -0
  123. package/.prettierrc +11 -0
  124. package/CLAUDE.md +2 -0
  125. package/README.md +64 -55
  126. package/SPEC.md +102 -61
  127. package/cli/bin/foliko.js +11 -11
  128. package/cli/src/commands/chat.js +143 -141
  129. package/cli/src/commands/list.js +93 -90
  130. package/cli/src/index.js +75 -75
  131. package/cli/src/ui/chat-ui.js +201 -199
  132. package/cli/src/utils/ansi.js +40 -40
  133. package/cli/src/utils/markdown.js +292 -296
  134. package/docker-compose.yml +1 -1
  135. package/docs/ai-sdk-optimization.md +655 -643
  136. package/docs/features.md +80 -80
  137. package/docs/quick-reference.md +49 -46
  138. package/docs/user-manual.md +411 -380
  139. package/examples/ambient-example.js +194 -196
  140. package/examples/basic.js +50 -45
  141. package/examples/bootstrap.js +121 -112
  142. package/examples/mcp-example.js +19 -16
  143. package/examples/skill-example.js +20 -20
  144. package/examples/test-chat.js +137 -135
  145. package/examples/test-mcp.js +85 -79
  146. package/examples/test-reload.js +59 -61
  147. package/examples/test-telegram.js +50 -50
  148. package/examples/test-tg-bot.js +45 -42
  149. package/examples/test-tg-simple.js +47 -46
  150. package/examples/test-tg.js +62 -62
  151. package/examples/test-think.js +43 -37
  152. package/examples/test-web-plugin.js +103 -98
  153. package/examples/test-weixin-feishu.js +103 -100
  154. package/examples/workflow.js +158 -158
  155. package/package.json +37 -3
  156. package/plugins/ai-plugin.js +102 -100
  157. package/plugins/ambient-agent/EventWatcher.js +113 -0
  158. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  159. package/plugins/ambient-agent/GoalManager.js +197 -0
  160. package/plugins/ambient-agent/Reflector.js +95 -0
  161. package/plugins/ambient-agent/StateStore.js +90 -0
  162. package/plugins/ambient-agent/constants.js +101 -0
  163. package/plugins/ambient-agent/index.js +579 -0
  164. package/plugins/audit-plugin.js +187 -187
  165. package/plugins/default-plugins.js +662 -649
  166. package/plugins/email/constants.js +64 -0
  167. package/plugins/email/handlers.js +461 -0
  168. package/plugins/email/index.js +278 -0
  169. package/plugins/email/monitor.js +269 -0
  170. package/plugins/email/parser.js +138 -0
  171. package/plugins/email/reply.js +151 -0
  172. package/plugins/email/utils.js +124 -0
  173. package/plugins/feishu-plugin.js +481 -477
  174. package/plugins/file-system-plugin.js +826 -476
  175. package/plugins/install-plugin.js +199 -197
  176. package/plugins/python-executor-plugin.js +367 -365
  177. package/plugins/python-plugin-loader.js +481 -479
  178. package/plugins/rules-plugin.js +294 -292
  179. package/plugins/scheduler-plugin.js +691 -689
  180. package/plugins/session-plugin.js +369 -367
  181. package/plugins/shell-executor-plugin.js +197 -197
  182. package/plugins/storage-plugin.js +240 -238
  183. package/plugins/subagent-plugin.js +845 -785
  184. package/plugins/telegram-plugin.js +482 -475
  185. package/plugins/think-plugin.js +345 -343
  186. package/plugins/tools-plugin.js +196 -194
  187. package/plugins/web-plugin.js +606 -604
  188. package/plugins/weixin-plugin.js +545 -538
  189. package/reports/system-health-report-20260401.md +79 -0
  190. package/skills/ambient-agent/SKILL.md +49 -39
  191. package/skills/foliko-dev/AGENTS.md +64 -61
  192. package/skills/foliko-dev/SKILL.md +125 -119
  193. package/skills/mcp-usage/SKILL.md +19 -17
  194. package/skills/python-plugin-dev/SKILL.md +16 -15
  195. package/skills/skill-guide/SKILL.md +12 -12
  196. package/skills/subagent-guide/SKILL.md +237 -0
  197. package/skills/workflow-guide/SKILL.md +90 -45
  198. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  199. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  200. package/src/capabilities/index.js +11 -11
  201. package/src/capabilities/skill-manager.js +609 -595
  202. package/src/capabilities/workflow-engine.js +1109 -1195
  203. package/src/core/agent-chat.js +882 -735
  204. package/src/core/agent.js +892 -688
  205. package/src/core/framework.js +465 -431
  206. package/src/core/index.js +19 -19
  207. package/src/core/plugin-base.js +219 -219
  208. package/src/core/plugin-manager.js +863 -767
  209. package/src/core/provider.js +114 -111
  210. package/src/core/sub-agent-config.js +264 -0
  211. package/src/core/system-prompt-builder.js +120 -0
  212. package/src/core/tool-registry.js +517 -134
  213. package/src/core/tool-router.js +297 -216
  214. package/src/executors/executor-base.js +12 -12
  215. package/src/executors/mcp-executor.js +741 -729
  216. package/src/index.js +25 -37
  217. package/src/utils/circuit-breaker.js +301 -0
  218. package/src/utils/error-boundary.js +363 -0
  219. package/src/utils/error.js +374 -0
  220. package/src/utils/event-emitter.js +97 -97
  221. package/src/utils/id.js +133 -0
  222. package/src/utils/index.js +217 -3
  223. package/src/utils/logger.js +181 -0
  224. package/src/utils/plugin-helpers.js +90 -0
  225. package/src/utils/retry.js +122 -0
  226. package/src/utils/sandbox.js +292 -0
  227. package/test/tool-registry-validation.test.js +218 -0
  228. package/test_report.md +70 -0
  229. package/website/docs/api.html +169 -107
  230. package/website/docs/configuration.html +296 -144
  231. package/website/docs/plugin-development.html +154 -85
  232. package/website/docs/project-structure.html +110 -109
  233. package/website/docs/skill-development.html +117 -61
  234. package/website/index.html +209 -205
  235. package/website/script.js +136 -133
  236. package/website/styles.css +1 -1
  237. package/plugins/ambient-agent-plugin.js +0 -1565
  238. package/plugins/email.js +0 -1142
@@ -1,475 +1,482 @@
1
- /**
2
- * Telegram 插件
3
- * 支持绑定主Agent进行持续对话
4
- *
5
- * 配置:
6
- * - botToken: Telegram Bot Token (必需)
7
- * - allowedChats: 允许的聊天ID数组
8
- * - groupMode: 是否启用群组模式
9
- * - prefix: 命令前缀
10
- */
11
-
12
- const { Plugin } = require('../src/core/plugin-base')
13
- const { z } = require('zod')
14
-
15
- // 转义 MarkdownV2 特殊字符
16
- function escapeMarkdown(text) {
17
- if (!text) return ''
18
- return text.replace(/([\_*\[\]()~`>#+\-=|{}.!])/g, '\\$1')
19
- }
20
-
21
- class TelegramPlugin extends Plugin {
22
- constructor(config = {}) {
23
- super()
24
- this.name = 'telegram'
25
- this.version = '2.1.0'
26
- this.description = 'Telegram 对话插件,绑定主Agent进行持续对话'
27
- this.priority = 80
28
- this.enabled = false
29
- this.systemPrompt = `你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。
30
-
31
- **命令执行规范:**
32
- - 执行 npx skills add / npx skills remove / npx skills list 命令时,必须自动添加参数:-a openclaw -y
33
- - 禁止添加 -g 参数
34
- - 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`
35
-
36
- this.config = {
37
- botToken: config.botToken || process.env.TELEGRAM_BOT_TOKEN,
38
- allowedChats: config.allowedChats || [],
39
- groupMode: config.groupMode || false,
40
- prefix: config.prefix || '/'
41
- }
42
-
43
- this._framework = null
44
- this._bot = null
45
- this._sessionPlugin = null
46
- this._sessionDeleteHandler = null
47
- this._sessionAgents = new Map()
48
- }
49
-
50
- install(framework) {
51
- this._framework = framework
52
- return this
53
- }
54
-
55
- start(framework) {
56
- if (!this.config.botToken) {
57
- console.warn('[Telegram] No bot token. Set TELEGRAM_BOT_TOKEN env var.')
58
- return this
59
- }
60
-
61
- this._framework = framework
62
- this._sessionPlugin = framework.pluginManager.get('session')
63
-
64
- if (this._sessionPlugin) {
65
- this._sessionDeleteHandler = (sessionId) => {
66
- if (sessionId.startsWith('telegram_')) {
67
- console.log(`[Telegram] Session deleted: ${sessionId}`)
68
- }
69
- }
70
- this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
71
- }
72
-
73
- this._initBot()
74
- return this
75
- }
76
-
77
- _initBot() {
78
- try {
79
- const TelegramBot = require('node-telegram-bot-api')
80
- this._bot = new TelegramBot(this.config.botToken, { polling: true })
81
-
82
- console.log('[Telegram] Bot started successfully')
83
-
84
- this._bot.setMyCommands([
85
- { command: 'start', description: '显示帮助信息' },
86
- { command: 'clear', description: '清除对话历史' },
87
- { command: 'history', description: '查看对话状态' }
88
- ]).catch((err) => {
89
- console.error('[Telegram] Failed to register commands:', err.message)
90
- })
91
-
92
- this._bot.on('message', (msg) => this._handleMessage(msg))
93
- this._bot.on('edit_message', (msg) => this._handleEdit(msg))
94
- this._bot.on('callback_query', (query) => this._handleCallback(query))
95
- this._bot.on('polling_error', (err) => console.error('[Telegram] Polling error:', err.message))
96
- this._bot.on('error', (err) => console.error('[Telegram] Bot error:', err.message))
97
-
98
- if (this._framework) {
99
- this._framework.on('agent:created', (agent) => {
100
- console.log('[Telegram] New agent created:', agent.name)
101
- })
102
-
103
- // 监听统一通知事件
104
- this._framework.on('notification', async (data) => {
105
- await this._handleNotification(data)
106
- })
107
-
108
- // 监听 webhook 事件
109
- this._framework.on('webhook:received', async (data) => {
110
- await this._handleWebhookNotification(data)
111
- })
112
- }
113
- } catch (err) {
114
- console.error('[Telegram] Failed to initialize bot:', err.message)
115
- }
116
- }
117
-
118
- /**
119
- * 处理统一通知
120
- */
121
- async _handleNotification(data) {
122
- const { title, message, source, level } = data
123
-
124
- if (!this._bot) {
125
- console.warn('[Telegram] Bot not ready, cannot send notification')
126
- return
127
- }
128
-
129
- // 确定 chatId
130
- let chatId = null
131
- let effectiveSessionId = data.sessionId
132
-
133
- // 如果没有 sessionId,尝试从执行上下文获取
134
- if (!effectiveSessionId) {
135
- const ctx = this._framework.getExecutionContext()
136
- if (ctx?.sessionId) {
137
- effectiveSessionId = ctx.sessionId
138
- }
139
- }
140
-
141
- if (effectiveSessionId && effectiveSessionId.startsWith('telegram_')) {
142
- chatId = effectiveSessionId.replace('telegram_', '')
143
- } else if (this._sessionPlugin) {
144
- // 获取最近的 telegram 会话
145
- const sessions = this._sessionPlugin.listSessions()
146
- .filter(s => s.id.startsWith('telegram_'))
147
- .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
148
- if (sessions.length > 0) {
149
- chatId = sessions[0].id.replace('telegram_', '')
150
- }
151
- }
152
-
153
- if (!chatId) {
154
- console.warn('[Telegram] No telegram session found for notification')
155
- return
156
- }
157
-
158
- // 格式化通知消息
159
- const levelEmoji = {
160
- info: 'ℹ️',
161
- warning: '⚠️',
162
- success: '✅',
163
- error: '❌'
164
- }
165
- const emoji = levelEmoji[level] || 'ℹ️'
166
- const notificationText = `${emoji} [${source}] ${title}\n\n${message}`
167
-
168
- try {
169
- await this._bot.sendMessage(chatId, notificationText)
170
- console.log(`[Telegram] Notification sent to chat ${chatId}`)
171
- } catch (err) {
172
- console.error(`[Telegram] Failed to send notification:`, err.message)
173
- }
174
- }
175
-
176
- /**
177
- * 处理 webhook 通知
178
- */
179
- async _handleWebhookNotification(data) {
180
- const { data: webhookData, response, sessionId } = data
181
-
182
- if (!sessionId || !sessionId.startsWith('telegram_')) {
183
- return
184
- }
185
-
186
- const chatId = sessionId.replace('telegram_', '')
187
- const notificationText = `📥 [Webhook 接收]\n\n路径: ${webhookData.path}\n方法: ${webhookData.method}\n\n处理结果: ${response || '处理中...'}`
188
-
189
- try {
190
- await this._bot.sendMessage(chatId, notificationText)
191
- console.log(`[Telegram] Webhook notification sent to chat ${chatId}`)
192
- } catch (err) {
193
- console.error(`[Telegram] Failed to send webhook notification:`, err.message)
194
- }
195
- }
196
-
197
- _getMainAgent() {
198
- if (this._framework._mainAgent) return this._framework._mainAgent
199
- const agents = this._framework._agents || []
200
- return agents.length > 0 ? agents[agents.length - 1] : null
201
- }
202
-
203
- _getSessionAgent(chatId) {
204
- if (this._sessionAgents.has(chatId)) {
205
- return { agent: this._sessionAgents.get(chatId), sessionId: `telegram_${chatId}` }
206
- }
207
-
208
- const agent = this._framework.createSessionAgent(`telegram_${chatId}`, {
209
- systemPrompt: this.systemPrompt,
210
- sharedPrompt: `工作目录: {{WORK_DIR}}`,
211
- metadata: { WORK_DIR: process.cwd() }
212
- })
213
- this._sessionAgents.set(chatId, agent)
214
-
215
- if (this._sessionPlugin) {
216
- this._sessionPlugin.getOrCreateSession(`telegram_${chatId}`, {
217
- metadata: { platform: 'telegram', chatId }
218
- })
219
- }
220
-
221
- return { agent, sessionId: `telegram_${chatId}` }
222
- }
223
-
224
- async _handleMessage(msg) {
225
- if (!msg || !msg.chat) return
226
- const chatId = msg.chat.id.toString()
227
-
228
- if (msg.photo) { await this._handlePhoto(msg); return }
229
- if (msg.document) { await this._handleDocument(msg); return }
230
- if (!msg.text) return
231
-
232
- const text = msg.text.trim()
233
- if (text.startsWith(this.config.prefix)) {
234
- await this._handleCommand(msg)
235
- return
236
- }
237
-
238
- if (msg.chat.type === 'group' || msg.chat.type === 'supergroup') {
239
- if (!this.config.groupMode) return
240
- }
241
-
242
- if (!this._checkPermission(chatId)) {
243
- await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
244
- return
245
- }
246
-
247
- await this._processChat(chatId, text, msg.message_id)
248
- }
249
-
250
- async _handleCommand(msg) {
251
- const chatId = msg.chat.id.toString()
252
- const text = msg.text.trim()
253
- const parts = text.split(' ')
254
- const command = parts[0].substring(1)
255
- const args = parts.slice(1).join(' ')
256
-
257
- switch (command.toLowerCase()) {
258
- case 'start':
259
- await this._sendMessage(chatId,
260
- '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
261
- msg.message_id)
262
- break
263
- case 'clear':
264
- this._clearSession(chatId)
265
- await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
266
- break
267
- case 'history':
268
- if (this._sessionPlugin) {
269
- const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
270
- const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
271
- await this._sendMessage(chatId,
272
- `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
273
- msg.message_id)
274
- } else {
275
- await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
276
- }
277
- break
278
- default:
279
- await this._processChat(chatId, text, msg.message_id)
280
- }
281
- }
282
-
283
- async _processChat(chatId, text, replyToMessageId) {
284
- const sessionInfo = this._getSessionAgent(chatId)
285
- if (!sessionInfo) {
286
- await this._sendMessage(chatId, '❌ AI 服务未初始化', replyToMessageId)
287
- return
288
- }
289
-
290
- const { agent, sessionId } = sessionInfo
291
-
292
- if (this._sessionPlugin) {
293
- this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
294
- }
295
-
296
- let thinkingMsg
297
- try {
298
- thinkingMsg = await this._bot.sendMessage(chatId, '🤔 思考中...', {
299
- reply_to_message_id: replyToMessageId
300
- })
301
- } catch (err) {
302
- console.error('[Telegram] Send thinking error:', err.message)
303
- return
304
- }
305
-
306
- try {
307
- let fullResponse = ''
308
- for await (const chunk of agent.chatStream(text, { sessionId })) {
309
- if (chunk.type === 'text' && chunk.text) {
310
- fullResponse += chunk.text
311
- if (fullResponse.length % 100 === 0) {
312
- try {
313
- await this._bot.editMessageText(`📝 ${escapeMarkdown(fullResponse)}▌`, {
314
- chat_id: chatId,
315
- message_id: thinkingMsg.message_id
316
- })
317
- } catch (e) { /* ignore */ }
318
- }
319
- }
320
- }
321
-
322
- if (this._sessionPlugin) {
323
- this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
324
- }
325
-
326
- const safeResponse = escapeMarkdown(fullResponse) || '抱歉,我没有收到有效的回复。'
327
- await this._bot.editMessageText(safeResponse, {
328
- chat_id: chatId,
329
- message_id: thinkingMsg.message_id,
330
- parse_mode: 'MarkdownV2'
331
- })
332
- } catch (err) {
333
- console.error('[Telegram] Chat error:', err)
334
- await this._bot.editMessageText(escapeMarkdown(`❌ 发生错误:${err.message}`), {
335
- chat_id: chatId,
336
- message_id: thinkingMsg.message_id
337
- })
338
- }
339
- }
340
-
341
- async _handleEdit(msg) { /* 支持编辑后重新处理 */ }
342
-
343
- async _handleCallback(query) {
344
- await this._bot.answerCallbackQuery(query.id, { text: '处理中...' })
345
- }
346
-
347
- _checkPermission(chatId) {
348
- return this.config.allowedChats.length === 0 || this.config.allowedChats.includes(chatId)
349
- }
350
-
351
- async _handlePhoto(msg) {
352
- const chatId = msg.chat.id.toString()
353
- if (!this._checkPermission(chatId)) {
354
- await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
355
- return
356
- }
357
-
358
- const caption = msg.caption?.trim() || ''
359
- const photo = msg.photo[msg.photo.length - 1]
360
-
361
- try {
362
- const filePath = await this._downloadFile(photo.file_id, '.jpg', 'telegram_images')
363
- await this._sendMessage(chatId, `收到图片${caption ? ': ' + caption : ''}\n已保存至: ${filePath}`, msg.message_id)
364
- if (caption) {
365
- await this._processChat(chatId, `图片:${filePath}, ${caption}`, msg.message_id)
366
- }
367
- } catch (err) {
368
- console.error('[Telegram] Failed to save photo:', err.message)
369
- await this._sendMessage(chatId, '图片保存失败: ' + err.message, msg.message_id)
370
- }
371
- }
372
-
373
- async _handleDocument(msg) {
374
- const chatId = msg.chat.id.toString()
375
- if (!this._checkPermission(chatId)) {
376
- await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
377
- return
378
- }
379
-
380
- const fileName = msg.document.file_name || '未命名文件'
381
- const caption = msg.caption?.trim() || ''
382
- const ext = fileName.includes('.') ? fileName.split('.').pop() : 'bin'
383
-
384
- try {
385
- const filePath = await this._downloadFile(msg.document.file_id, ext, 'telegram_documents')
386
- await this._sendMessage(chatId, `收到文件: ${fileName}\n已保存至: ${filePath}${caption ? '\n\n说明: ' + caption : ''}`, msg.message_id)
387
- if (caption) {
388
- await this._processChat(chatId, `文件:${filePath}, ${caption}`, msg.message_id)
389
- }
390
- } catch (err) {
391
- console.error('[Telegram] Failed to save document:', err.message)
392
- await this._sendMessage(chatId, '文件保存失败: ' + err.message, msg.message_id)
393
- }
394
- }
395
-
396
- async _downloadFile(fileId, ext, subDir) {
397
- const path = require('path')
398
- const fs = require('fs')
399
- const saveDir = path.join(process.cwd(), '.agent', 'data', subDir)
400
- if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
401
-
402
- const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`
403
- const filePath = path.join(saveDir, fileName)
404
-
405
- const savedPath = await this._bot.downloadFile(fileId, saveDir)
406
- if (savedPath !== filePath) fs.renameSync(savedPath, filePath)
407
- return filePath
408
- }
409
-
410
- _clearSession(chatId) {
411
- if (this._sessionPlugin) {
412
- this._sessionPlugin.deleteSession(`telegram_${chatId}`)
413
- }
414
- }
415
-
416
- async _sendMessage(chatId, text, replyToMessageId = null) {
417
- if (!this._bot) return
418
- return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
419
- }
420
-
421
- getStatus() {
422
- let sessions = []
423
- if (this._sessionPlugin) {
424
- sessions = this._sessionPlugin.listSessions()
425
- .filter(s => s.id.startsWith('telegram_'))
426
- .map(s => ({
427
- chatId: s.id.replace('telegram_', ''),
428
- historyLength: s.messageCount,
429
- lastActive: s.lastActive
430
- }))
431
- }
432
- return {
433
- connected: !!this._bot,
434
- sessionCount: sessions.length,
435
- sessions,
436
- config: {
437
- groupMode: this.config.groupMode,
438
- prefix: this.config.prefix,
439
- allowedChatsCount: this.config.allowedChats.length
440
- }
441
- }
442
- }
443
-
444
- stopBot() {
445
- if (this._bot) {
446
- this._bot.stopPolling()
447
- this._bot = null
448
- console.log('[Telegram] Bot stopped')
449
- }
450
- }
451
-
452
- reload(framework) {
453
- this._framework = framework
454
- if (this.config.botToken) {
455
- this.stopBot()
456
- this._initBot()
457
- }
458
- }
459
-
460
- uninstall() {
461
- for (const agent of this._sessionAgents.values()) {
462
- agent.destroy()
463
- }
464
- this._sessionAgents.clear()
465
- this.stopBot()
466
- if (this._sessionPlugin && this._sessionDeleteHandler) {
467
- this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
468
- this._sessionDeleteHandler = null
469
- }
470
- this._sessionPlugin = null
471
- this._framework = null
472
- }
473
- }
474
-
475
- module.exports = { TelegramPlugin }
1
+ /**
2
+ * Telegram 插件
3
+ * 支持绑定主Agent进行持续对话
4
+ *
5
+ * 配置:
6
+ * - botToken: Telegram Bot Token (必需)
7
+ * - allowedChats: 允许的聊天ID数组
8
+ * - groupMode: 是否启用群组模式
9
+ * - prefix: 命令前缀
10
+ */
11
+
12
+ const { Plugin } = require('../src/core/plugin-base')
13
+ const { logger } = require('../src/utils/logger')
14
+ const log = logger.child('Telegram')
15
+ const { z } = require('zod')
16
+
17
+ // 转义 MarkdownV2 特殊字符
18
+ function escapeMarkdown(text) {
19
+ if (!text) return ''
20
+ return text.replace(/([\_*\[\]()~`>#+\-=|{}.!])/g, '\\$1')
21
+ }
22
+
23
+ class TelegramPlugin extends Plugin {
24
+ constructor(config = {}) {
25
+ super()
26
+ this.name = 'telegram'
27
+ this.version = '2.1.0'
28
+ this.description = 'Telegram 对话插件,绑定主Agent进行持续对话'
29
+ this.priority = 80
30
+ this.enabled = false
31
+ this.systemPrompt = `你是一个有帮助的AI助手。回复内容不要使用markdown格式文本。
32
+
33
+ **重要:** 子Agent 匹配规则必须遵守:
34
+ - 根据【子 Agent 匹配表】,将任务委托给最匹配的子Agent处理
35
+ - 使用 subagent_call 工具并指定 agentName 来委托任务
36
+ - 只有当没有匹配的子Agent时,才直接调用工具
37
+
38
+ **命令执行规范:**
39
+ - 执行 npx skills add / npx skills remove / npx skills list 命令时,必须自动添加参数:-a openclaw -y
40
+ - 禁止添加 -g 参数
41
+ - 例如:npx skills add xxx 应该执行为 npx skills add xxx -a openclaw -y`
42
+
43
+ this.config = {
44
+ botToken: config.botToken || process.env.TELEGRAM_BOT_TOKEN,
45
+ allowedChats: config.allowedChats || [],
46
+ groupMode: config.groupMode || false,
47
+ prefix: config.prefix || '/'
48
+ }
49
+
50
+ this._framework = null
51
+ this._bot = null
52
+ this._sessionPlugin = null
53
+ this._sessionDeleteHandler = null
54
+ this._sessionAgents = new Map()
55
+ }
56
+
57
+ install(framework) {
58
+ this._framework = framework
59
+ return this
60
+ }
61
+
62
+ start(framework) {
63
+ if (!this.config.botToken) {
64
+ log.warn(' No bot token. Set TELEGRAM_BOT_TOKEN env var.')
65
+ return this
66
+ }
67
+
68
+ this._framework = framework
69
+ this._sessionPlugin = framework.pluginManager.get('session')
70
+
71
+ if (this._sessionPlugin) {
72
+ this._sessionDeleteHandler = (sessionId) => {
73
+ if (sessionId.startsWith('telegram_')) {
74
+ log.info(` Session deleted: ${sessionId}`)
75
+ }
76
+ }
77
+ this._sessionPlugin.on('session:deleted', this._sessionDeleteHandler)
78
+ }
79
+
80
+ this._initBot()
81
+ return this
82
+ }
83
+
84
+ _initBot() {
85
+ try {
86
+ const TelegramBot = require('node-telegram-bot-api')
87
+ this._bot = new TelegramBot(this.config.botToken, { polling: true })
88
+
89
+ log.info(' Bot started successfully')
90
+
91
+ this._bot.setMyCommands([
92
+ { command: 'start', description: '显示帮助信息' },
93
+ { command: 'clear', description: '清除对话历史' },
94
+ { command: 'history', description: '查看对话状态' }
95
+ ]).catch((err) => {
96
+ log.error(' Failed to register commands:', err.message)
97
+ })
98
+
99
+ this._bot.on('message', (msg) => this._handleMessage(msg))
100
+ this._bot.on('edit_message', (msg) => this._handleEdit(msg))
101
+ this._bot.on('callback_query', (query) => this._handleCallback(query))
102
+ this._bot.on('polling_error', (err) => log.error(' Polling error:', err.message))
103
+ this._bot.on('error', (err) => log.error(' Bot error:', err.message))
104
+
105
+ if (this._framework) {
106
+ this._framework.on('agent:created', (agent) => {
107
+ log.info(' New agent created:', agent.name)
108
+ })
109
+
110
+ // 监听统一通知事件
111
+ this._framework.on('notification', async (data) => {
112
+ await this._handleNotification(data)
113
+ })
114
+
115
+ // 监听 webhook 事件
116
+ this._framework.on('webhook:received', async (data) => {
117
+ await this._handleWebhookNotification(data)
118
+ })
119
+ }
120
+ } catch (err) {
121
+ log.error(' Failed to initialize bot:', err.message)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * 处理统一通知
127
+ */
128
+ async _handleNotification(data) {
129
+ const { title, message, source, level } = data
130
+
131
+ if (!this._bot) {
132
+ log.warn(' Bot not ready, cannot send notification')
133
+ return
134
+ }
135
+
136
+ // 确定 chatId
137
+ let chatId = null
138
+ let effectiveSessionId = data.sessionId
139
+
140
+ // 如果没有 sessionId,尝试从执行上下文获取
141
+ if (!effectiveSessionId) {
142
+ const ctx = this._framework.getExecutionContext()
143
+ if (ctx?.sessionId) {
144
+ effectiveSessionId = ctx.sessionId
145
+ }
146
+ }
147
+
148
+ if (effectiveSessionId && effectiveSessionId.startsWith('telegram_')) {
149
+ chatId = effectiveSessionId.replace('telegram_', '')
150
+ } else if (this._sessionPlugin) {
151
+ // 获取最近的 telegram 会话
152
+ const sessions = this._sessionPlugin.listSessions()
153
+ .filter(s => s.id.startsWith('telegram_'))
154
+ .sort((a, b) => new Date(b.lastActive).getTime() - new Date(a.lastActive).getTime())
155
+ if (sessions.length > 0) {
156
+ chatId = sessions[0].id.replace('telegram_', '')
157
+ }
158
+ }
159
+
160
+ if (!chatId) {
161
+ log.warn(' No telegram session found for notification')
162
+ return
163
+ }
164
+
165
+ // 格式化通知消息
166
+ const levelEmoji = {
167
+ info: 'ℹ️',
168
+ warning: '⚠️',
169
+ success: '✅',
170
+ error: '❌'
171
+ }
172
+ const emoji = levelEmoji[level] || 'ℹ️'
173
+ const notificationText = `${emoji} [${source}] ${title}\n\n${message}`
174
+
175
+ try {
176
+ await this._bot.sendMessage(chatId, notificationText)
177
+ log.info(` Notification sent to chat ${chatId}`)
178
+ } catch (err) {
179
+ log.error(` Failed to send notification:`, err.message)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 处理 webhook 通知
185
+ */
186
+ async _handleWebhookNotification(data) {
187
+ const { data: webhookData, response, sessionId } = data
188
+
189
+ if (!sessionId || !sessionId.startsWith('telegram_')) {
190
+ return
191
+ }
192
+
193
+ const chatId = sessionId.replace('telegram_', '')
194
+ const notificationText = `📥 [Webhook 接收]\n\n路径: ${webhookData.path}\n方法: ${webhookData.method}\n\n处理结果: ${response || '处理中...'}`
195
+
196
+ try {
197
+ await this._bot.sendMessage(chatId, notificationText)
198
+ log.info(` Webhook notification sent to chat ${chatId}`)
199
+ } catch (err) {
200
+ log.error(` Failed to send webhook notification:`, err.message)
201
+ }
202
+ }
203
+
204
+ _getMainAgent() {
205
+ if (this._framework._mainAgent) return this._framework._mainAgent
206
+ const agents = this._framework._agents || []
207
+ return agents.length > 0 ? agents[agents.length - 1] : null
208
+ }
209
+
210
+ _getSessionAgent(chatId) {
211
+ if (this._sessionAgents.has(chatId)) {
212
+ return { agent: this._sessionAgents.get(chatId), sessionId: `telegram_${chatId}` }
213
+ }
214
+
215
+ const agent = this._framework.createSessionAgent(`telegram_${chatId}`, {
216
+ systemPrompt: this.systemPrompt,
217
+ sharedPrompt: `工作目录: {{WORK_DIR}}`,
218
+ metadata: { WORK_DIR: process.cwd() }
219
+ })
220
+ this._sessionAgents.set(chatId, agent)
221
+
222
+ if (this._sessionPlugin) {
223
+ this._sessionPlugin.getOrCreateSession(`telegram_${chatId}`, {
224
+ metadata: { platform: 'telegram', chatId }
225
+ })
226
+ }
227
+
228
+ return { agent, sessionId: `telegram_${chatId}` }
229
+ }
230
+
231
+ async _handleMessage(msg) {
232
+ if (!msg || !msg.chat) return
233
+ const chatId = msg.chat.id.toString()
234
+
235
+ if (msg.photo) { await this._handlePhoto(msg); return }
236
+ if (msg.document) { await this._handleDocument(msg); return }
237
+ if (!msg.text) return
238
+
239
+ const text = msg.text.trim()
240
+ if (text.startsWith(this.config.prefix)) {
241
+ await this._handleCommand(msg)
242
+ return
243
+ }
244
+
245
+ if (msg.chat.type === 'group' || msg.chat.type === 'supergroup') {
246
+ if (!this.config.groupMode) return
247
+ }
248
+
249
+ if (!this._checkPermission(chatId)) {
250
+ await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
251
+ return
252
+ }
253
+
254
+ await this._processChat(chatId, text, msg.message_id)
255
+ }
256
+
257
+ async _handleCommand(msg) {
258
+ const chatId = msg.chat.id.toString()
259
+ const text = msg.text.trim()
260
+ const parts = text.split(' ')
261
+ const command = parts[0].substring(1)
262
+ const args = parts.slice(1).join(' ')
263
+
264
+ switch (command.toLowerCase()) {
265
+ case 'start':
266
+ await this._sendMessage(chatId,
267
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
268
+ msg.message_id)
269
+ break
270
+ case 'clear':
271
+ this._clearSession(chatId)
272
+ await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
273
+ break
274
+ case 'history':
275
+ if (this._sessionPlugin) {
276
+ const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
277
+ const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
278
+ await this._sendMessage(chatId,
279
+ `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
280
+ msg.message_id)
281
+ } else {
282
+ await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
283
+ }
284
+ break
285
+ default:
286
+ await this._processChat(chatId, text, msg.message_id)
287
+ }
288
+ }
289
+
290
+ async _processChat(chatId, text, replyToMessageId) {
291
+ const sessionInfo = this._getSessionAgent(chatId)
292
+ if (!sessionInfo) {
293
+ await this._sendMessage(chatId, '❌ AI 服务未初始化', replyToMessageId)
294
+ return
295
+ }
296
+
297
+ const { agent, sessionId } = sessionInfo
298
+
299
+ if (this._sessionPlugin) {
300
+ this._sessionPlugin.addMessage(sessionId, { role: 'user', content: text })
301
+ }
302
+
303
+ let thinkingMsg
304
+ try {
305
+ thinkingMsg = await this._bot.sendMessage(chatId, '🤔 思考中...', {
306
+ reply_to_message_id: replyToMessageId
307
+ })
308
+ } catch (err) {
309
+ log.error(' Send thinking error:', err.message)
310
+ return
311
+ }
312
+
313
+ try {
314
+ let fullResponse = ''
315
+ for await (const chunk of agent.chatStream(text, { sessionId })) {
316
+ if (chunk.type === 'text' && chunk.text) {
317
+ fullResponse += chunk.text
318
+ if (fullResponse.length % 100 === 0) {
319
+ try {
320
+ await this._bot.editMessageText(`📝 ${escapeMarkdown(fullResponse)}▌`, {
321
+ chat_id: chatId,
322
+ message_id: thinkingMsg.message_id
323
+ })
324
+ } catch (e) { /* ignore */ }
325
+ }
326
+ }
327
+ }
328
+
329
+ if (this._sessionPlugin) {
330
+ this._sessionPlugin.addMessage(sessionId, { role: 'assistant', content: fullResponse })
331
+ }
332
+
333
+ const safeResponse = escapeMarkdown(fullResponse) || '抱歉,我没有收到有效的回复。'
334
+ await this._bot.editMessageText(safeResponse, {
335
+ chat_id: chatId,
336
+ message_id: thinkingMsg.message_id,
337
+ parse_mode: 'MarkdownV2'
338
+ })
339
+ } catch (err) {
340
+ log.error(' Chat error:', err)
341
+ await this._bot.editMessageText(escapeMarkdown(`❌ 发生错误:${err.message}`), {
342
+ chat_id: chatId,
343
+ message_id: thinkingMsg.message_id
344
+ })
345
+ }
346
+ }
347
+
348
+ async _handleEdit(msg) { /* 支持编辑后重新处理 */ }
349
+
350
+ async _handleCallback(query) {
351
+ await this._bot.answerCallbackQuery(query.id, { text: '处理中...' })
352
+ }
353
+
354
+ _checkPermission(chatId) {
355
+ return this.config.allowedChats.length === 0 || this.config.allowedChats.includes(chatId)
356
+ }
357
+
358
+ async _handlePhoto(msg) {
359
+ const chatId = msg.chat.id.toString()
360
+ if (!this._checkPermission(chatId)) {
361
+ await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
362
+ return
363
+ }
364
+
365
+ const caption = msg.caption?.trim() || ''
366
+ const photo = msg.photo[msg.photo.length - 1]
367
+
368
+ try {
369
+ const filePath = await this._downloadFile(photo.file_id, '.jpg', 'telegram_images')
370
+ await this._sendMessage(chatId, `收到图片${caption ? ': ' + caption : ''}\n已保存至: ${filePath}`, msg.message_id)
371
+ if (caption) {
372
+ await this._processChat(chatId, `图片:${filePath}, ${caption}`, msg.message_id)
373
+ }
374
+ } catch (err) {
375
+ log.error(' Failed to save photo:', err.message)
376
+ await this._sendMessage(chatId, '图片保存失败: ' + err.message, msg.message_id)
377
+ }
378
+ }
379
+
380
+ async _handleDocument(msg) {
381
+ const chatId = msg.chat.id.toString()
382
+ if (!this._checkPermission(chatId)) {
383
+ await this._sendMessage(chatId, '抱歉,您没有权限使用此 Bot。', msg.message_id)
384
+ return
385
+ }
386
+
387
+ const fileName = msg.document.file_name || '未命名文件'
388
+ const caption = msg.caption?.trim() || ''
389
+ const ext = fileName.includes('.') ? fileName.split('.').pop() : 'bin'
390
+
391
+ try {
392
+ const filePath = await this._downloadFile(msg.document.file_id, ext, 'telegram_documents')
393
+ await this._sendMessage(chatId, `收到文件: ${fileName}\n已保存至: ${filePath}${caption ? '\n\n说明: ' + caption : ''}`, msg.message_id)
394
+ if (caption) {
395
+ await this._processChat(chatId, `文件:${filePath}, ${caption}`, msg.message_id)
396
+ }
397
+ } catch (err) {
398
+ log.error(' Failed to save document:', err.message)
399
+ await this._sendMessage(chatId, '文件保存失败: ' + err.message, msg.message_id)
400
+ }
401
+ }
402
+
403
+ async _downloadFile(fileId, ext, subDir) {
404
+ const path = require('path')
405
+ const fs = require('fs')
406
+ const saveDir = path.join(process.cwd(), '.agent', 'data', subDir)
407
+ if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir, { recursive: true })
408
+
409
+ const fileName = `${Date.now()}_${Math.random().toString(36).substring(7)}.${ext}`
410
+ const filePath = path.join(saveDir, fileName)
411
+
412
+ const savedPath = await this._bot.downloadFile(fileId, saveDir)
413
+ if (savedPath !== filePath) fs.renameSync(savedPath, filePath)
414
+ return filePath
415
+ }
416
+
417
+ _clearSession(chatId) {
418
+ if (this._sessionPlugin) {
419
+ this._sessionPlugin.deleteSession(`telegram_${chatId}`)
420
+ }
421
+ }
422
+
423
+ async _sendMessage(chatId, text, replyToMessageId = null) {
424
+ if (!this._bot) return
425
+ return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
426
+ }
427
+
428
+ getStatus() {
429
+ let sessions = []
430
+ if (this._sessionPlugin) {
431
+ sessions = this._sessionPlugin.listSessions()
432
+ .filter(s => s.id.startsWith('telegram_'))
433
+ .map(s => ({
434
+ chatId: s.id.replace('telegram_', ''),
435
+ historyLength: s.messageCount,
436
+ lastActive: s.lastActive
437
+ }))
438
+ }
439
+ return {
440
+ connected: !!this._bot,
441
+ sessionCount: sessions.length,
442
+ sessions,
443
+ config: {
444
+ groupMode: this.config.groupMode,
445
+ prefix: this.config.prefix,
446
+ allowedChatsCount: this.config.allowedChats.length
447
+ }
448
+ }
449
+ }
450
+
451
+ stopBot() {
452
+ if (this._bot) {
453
+ this._bot.stopPolling()
454
+ this._bot = null
455
+ log.info(' Bot stopped')
456
+ }
457
+ }
458
+
459
+ reload(framework) {
460
+ this._framework = framework
461
+ if (this.config.botToken) {
462
+ this.stopBot()
463
+ this._initBot()
464
+ }
465
+ }
466
+
467
+ uninstall() {
468
+ for (const agent of this._sessionAgents.values()) {
469
+ agent.destroy()
470
+ }
471
+ this._sessionAgents.clear()
472
+ this.stopBot()
473
+ if (this._sessionPlugin && this._sessionDeleteHandler) {
474
+ this._sessionPlugin.off('session:deleted', this._sessionDeleteHandler)
475
+ this._sessionDeleteHandler = null
476
+ }
477
+ this._sessionPlugin = null
478
+ this._framework = null
479
+ }
480
+ }
481
+
482
+ module.exports = { TelegramPlugin }