foliko 1.0.73 → 1.0.75

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 (237) 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 +157 -149
  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 +4 -4
  128. package/cli/src/commands/chat.js +53 -51
  129. package/cli/src/commands/list.js +40 -37
  130. package/cli/src/index.js +18 -18
  131. package/cli/src/ui/chat-ui.js +78 -76
  132. package/cli/src/utils/ansi.js +15 -15
  133. package/cli/src/utils/markdown.js +112 -116
  134. package/docker-compose.yml +1 -1
  135. package/docs/ai-sdk-optimization.md +655 -636
  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 +95 -97
  140. package/examples/basic.js +115 -110
  141. package/examples/bootstrap.js +52 -43
  142. package/examples/mcp-example.js +56 -53
  143. package/examples/skill-example.js +49 -49
  144. package/examples/test-chat.js +60 -58
  145. package/examples/test-mcp.js +49 -43
  146. package/examples/test-reload.js +38 -40
  147. package/examples/test-telegram.js +3 -3
  148. package/examples/test-tg-bot.js +7 -4
  149. package/examples/test-tg-simple.js +4 -3
  150. package/examples/test-tg.js +3 -3
  151. package/examples/test-think.js +13 -7
  152. package/examples/test-web-plugin.js +61 -56
  153. package/examples/test-weixin-feishu.js +40 -37
  154. package/examples/workflow.js +49 -49
  155. package/foliko-1.0.75.tgz +0 -0
  156. package/package.json +37 -3
  157. package/plugins/ai-plugin.js +7 -5
  158. package/plugins/ambient-agent/EventWatcher.js +113 -0
  159. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  160. package/plugins/ambient-agent/GoalManager.js +197 -0
  161. package/plugins/ambient-agent/Reflector.js +95 -0
  162. package/plugins/ambient-agent/StateStore.js +90 -0
  163. package/plugins/ambient-agent/constants.js +101 -0
  164. package/plugins/ambient-agent/index.js +579 -0
  165. package/plugins/default-plugins.js +62 -49
  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 +23 -19
  174. package/plugins/file-system-plugin.js +469 -120
  175. package/plugins/install-plugin.js +6 -4
  176. package/plugins/python-executor-plugin.js +3 -1
  177. package/plugins/python-plugin-loader.js +10 -8
  178. package/plugins/rules-plugin.js +5 -3
  179. package/plugins/scheduler-plugin.js +18 -16
  180. package/plugins/session-plugin.js +3 -1
  181. package/plugins/storage-plugin.js +5 -3
  182. package/plugins/subagent-plugin.js +152 -92
  183. package/plugins/telegram-plugin.js +26 -19
  184. package/plugins/think-plugin.js +4 -2
  185. package/plugins/tools-plugin.js +3 -1
  186. package/plugins/web-plugin.js +15 -13
  187. package/plugins/weixin-plugin.js +43 -36
  188. package/reports/system-health-report-20260401.md +79 -0
  189. package/skills/ambient-agent/SKILL.md +49 -39
  190. package/skills/foliko-dev/AGENTS.md +64 -61
  191. package/skills/foliko-dev/SKILL.md +125 -119
  192. package/skills/mcp-usage/SKILL.md +19 -17
  193. package/skills/python-plugin-dev/SKILL.md +16 -15
  194. package/skills/skill-guide/SKILL.md +12 -12
  195. package/skills/subagent-guide/SKILL.md +237 -0
  196. package/skills/workflow-guide/SKILL.md +90 -45
  197. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  198. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  199. package/src/capabilities/index.js +4 -4
  200. package/src/capabilities/skill-manager.js +211 -197
  201. package/src/capabilities/workflow-engine.js +461 -547
  202. package/src/core/agent-chat.js +426 -279
  203. package/src/core/agent.js +453 -248
  204. package/src/core/framework.js +183 -149
  205. package/src/core/index.js +8 -8
  206. package/src/core/plugin-base.js +52 -52
  207. package/src/core/plugin-manager.js +377 -281
  208. package/src/core/provider.js +35 -32
  209. package/src/core/sub-agent-config.js +264 -0
  210. package/src/core/system-prompt-builder.js +120 -0
  211. package/src/core/tool-registry.js +416 -33
  212. package/src/core/tool-router.js +149 -68
  213. package/src/executors/executor-base.js +58 -58
  214. package/src/executors/mcp-executor.js +269 -257
  215. package/src/index.js +5 -17
  216. package/src/utils/circuit-breaker.js +301 -0
  217. package/src/utils/error-boundary.js +363 -0
  218. package/src/utils/error.js +374 -0
  219. package/src/utils/event-emitter.js +20 -20
  220. package/src/utils/id.js +133 -0
  221. package/src/utils/index.js +217 -3
  222. package/src/utils/logger.js +181 -0
  223. package/src/utils/plugin-helpers.js +90 -0
  224. package/src/utils/retry.js +122 -0
  225. package/src/utils/sandbox.js +292 -0
  226. package/test/tool-registry-validation.test.js +218 -0
  227. package/test_report.md +70 -0
  228. package/website/docs/api.html +169 -107
  229. package/website/docs/configuration.html +296 -144
  230. package/website/docs/plugin-development.html +154 -85
  231. package/website/docs/project-structure.html +110 -109
  232. package/website/docs/skill-development.html +117 -61
  233. package/website/index.html +209 -205
  234. package/website/script.js +20 -17
  235. package/website/styles.css +1 -1
  236. package/plugins/ambient-agent-plugin.js +0 -1565
  237. package/plugins/email.js +0 -1142
package/plugins/email.js DELETED
@@ -1,1142 +0,0 @@
1
- /**
2
- * Email 插件
3
- * 邮件收发插件 - 支持读取和发送电子邮件、监控新邮件
4
- */
5
-
6
- const { Plugin } = require('../src/core/plugin-base')
7
- const { z } = require('zod')
8
-
9
- class EmailPlugin extends Plugin {
10
- constructor(config = {}) {
11
- super()
12
- this.name = 'email'
13
- this.version = '1.1.0'
14
- this.description = `邮件收发插件 - 支持读取和发送电子邮件、监控新邮件
15
- 功能:
16
- - 发送邮件支持附件(本地文件、远程URL、Base64)
17
- - 读取邮件(IMAP协议)
18
- - 监控新邮件(支持IMAP IDLE推送和定时轮询)
19
- 发送邮件支持附件:
20
- - attachments.path: 本地文件路径
21
- - attachments.url: 远程图片/文件URL(自动下载)
22
- - attachments.content: Base64内容
23
- - attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
24
- this.priority = 10
25
- this.enabled = false
26
-
27
- // 邮件监控状态
28
- this._watchInterval = null
29
- this._lastSeenUid = null
30
- this._watchConfig = null
31
- this._watchEnabled = false
32
- this._checkInProgress = false // 防止并发检查
33
- this._recentlyEmailed = new Set() // 去重:最近已处理的 messageId(短TTL,用于快速去重)
34
- this._processedEmails = new Set() // 已处理(已回复)的邮件,不会重复处理(长TTL)
35
- }
36
-
37
- install(framework) {
38
- this._framework = framework
39
- this._registerTools()
40
- return this
41
- }
42
-
43
- start(framework) {
44
- // 自动启动邮件监控(如果配置了 IMAP 且尚未运行)
45
- if (this._watchEnabled) {
46
- console.log('[Email] Email watch already running, skipping auto-start')
47
- return this
48
- }
49
-
50
- // if (process.env.IMAP_USER && process.env.IMAP_PASS) {
51
- // console.log('[Email] Auto-starting email watch...')
52
- // this._startEmailWatch({
53
- // interval: 60,
54
- // host: process.env.IMAP_HOST,
55
- // port: parseInt(process.env.IMAP_PORT) || 993,
56
- // user: process.env.IMAP_USER,
57
- // password: process.env.IMAP_PASS,
58
- // box: 'INBOX'
59
- // })
60
- // } else {
61
- // console.log('[Email] IMAP credentials not configured, skipping auto-start')
62
- // }
63
- return this
64
- }
65
-
66
- /**
67
- * 发送邮件事件
68
- */
69
- _emitEmailReceived(email) {
70
- if (!this._framework) return
71
- // 去重:如果最近已处理过这个 messageId,跳过
72
- const msgId = email.messageId || email.uid
73
- //console.log(`[Email] _emitEmailReceived: msgId=${msgId}, messageId=${email.messageId}, uid=${email.uid}`)
74
-
75
- // 优先检查是否已处理过(长TTL,防止服务器标记失败导致重复处理)
76
- if (msgId && this._processedEmails.has(msgId)) {
77
- console.log(`[Email] 跳过已处理的邮件: ${email.subject} (${msgId})`)
78
- return
79
- }
80
-
81
- // 快速去重(短TTL,防止同一批次重复触发)
82
- if (msgId && this._recentlyEmailed.has(msgId)) {
83
- //console.log(`[Email] 跳过重复邮件: ${email.subject} (${msgId})`)
84
- return
85
- }
86
-
87
- // 标记为已处理
88
- if (msgId) {
89
- this._recentlyEmailed.add(msgId)
90
- // 5分钟后从 Set 中移除,允许重新处理(适用于邮件被删除后重新收到的极端情况)
91
- setTimeout(() => {
92
- this._recentlyEmailed.delete(msgId)
93
- }, 5 * 60 * 1000)
94
- }
95
-
96
- this._framework.emit('email:received', {
97
- email,
98
- timestamp: new Date()
99
- })
100
- }
101
-
102
- _registerTools() {
103
- // 发送邮件工具
104
- this._framework.registerTool({
105
- name: 'email_send',
106
- description: '发送电子邮件,无须用户确认',
107
- inputSchema: z.object({
108
- to: z.string().describe('收件人邮箱地址'),
109
- subject: z.string().describe('邮件主题'),
110
- body: z.string().describe('邮件正文内容'),
111
- cc: z.string().optional().describe('抄送邮箱地址,多个用逗号分隔'),
112
- bcc: z.string().optional().describe('密送邮箱地址,多个用逗号分隔'),
113
- isHtml: z.boolean().optional().describe('是否为HTML格式,默认false'),
114
- attachments: z.array(z.object({
115
- filename: z.string().describe('附件文件名'),
116
- path: z.string().optional().describe('本地文件路径'),
117
- url: z.string().optional().describe('远程图片URL'),
118
- content: z.string().optional().describe('Base64编码内容(可带前缀如 data:image/png;base64,)'),
119
- cid: z.string().optional().describe('嵌入式图片的CID(用于在HTML中嵌入图片,如 <img src="cid:xxx">)')
120
- })).optional().describe('附件列表,支持本地文件、远程URL或Base64内容')
121
- }),
122
- execute: async (args) => {
123
- return this._sendEmail(args)
124
- }
125
- })
126
-
127
- // 读取邮件工具
128
- this._framework.registerTool({
129
- name: 'email_read',
130
- description: '读取电子邮件(支持IMAP协议)',
131
- inputSchema: z.object({
132
- // host: z.string().optional().describe('IMAP服务器地址,默认从配置获取'),
133
- // port: z.number().optional().describe('IMAP端口,默认993'),
134
- // user: z.string().optional().describe('邮箱用户名,默认从配置获取'),
135
- // password: z.string().optional().describe('邮箱密码,默认从配置获取'),
136
- box: z.string().optional().describe('邮箱文件夹,默认INBOX'),
137
- limit: z.number().optional().describe('读取邮件数量,默认10'),
138
- unreadOnly: z.boolean().optional().describe('仅读取未读邮件,默认false'),
139
- searchCriteria: z.string().optional().describe('搜索条件,如 "UNSEEN", "FROM sender@example.com"')
140
- }),
141
- execute: async (args) => {
142
- return this._readEmails(args)
143
- }
144
- })
145
-
146
- // 获取未读邮件数量
147
- this._framework.registerTool({
148
- name: 'email_unread_count',
149
- description: '获取邮箱未读邮件数量',
150
- inputSchema: z.object({
151
- // host: z.string().optional().describe('IMAP服务器地址'),
152
- // port: z.number().optional().describe('IMAP端口'),
153
- // user: z.string().optional().describe('邮箱用户名'),
154
- // password: z.string().optional().describe('邮箱密码'),
155
- box: z.string().optional().describe('邮箱文件夹,默认INBOX')
156
- }),
157
- execute: async (args) => {
158
- return this._getUnreadCount(args)
159
- }
160
- })
161
-
162
- // 标记邮件为已读
163
- this._framework.registerTool({
164
- name: 'email_mark_read',
165
- description: '标记邮件为已读',
166
- inputSchema: z.object({
167
- // host: z.string().optional().describe('IMAP服务器地址'),
168
- // port: z.number().optional().describe('IMAP端口'),
169
- // user: z.string().optional().describe('邮箱用户名'),
170
- // password: z.string().optional().describe('邮箱密码'),
171
- messageId: z.string().describe('邮件UID或序列号')
172
- }),
173
- execute: async (args) => {
174
- return this._markAsRead(args)
175
- }
176
- })
177
-
178
- // 删除邮件
179
- this._framework.registerTool({
180
- name: 'email_delete',
181
- description: '删除邮件(标记为已删除,然后永久删除)',
182
- inputSchema: z.object({
183
- messageId: z.string().describe('邮件UID或序列号'),
184
- box: z.string().optional().describe('邮箱文件夹,默认INBOX')
185
- }),
186
- execute: async (args) => {
187
- return this._deleteEmail(args)
188
- }
189
- })
190
-
191
- // 配置邮箱连接
192
- this._framework.registerTool({
193
- name: 'email_configure',
194
- description: '查看邮箱配置(实际配置通过环境变量设置)',
195
- inputSchema: z.object({}),
196
- execute: async () => {
197
- return {
198
- success: true,
199
- message: '邮箱配置通过环境变量设置',
200
- config: {
201
- smtp_host: process.env.SMTP_HOST || 'smtp.gmail.com',
202
- smtp_port: process.env.SMTP_PORT || 587,
203
- smtp_secure: process.env.SMTP_SECURE || 'false',
204
- imap_host: process.env.IMAP_HOST || 'imap.gmail.com',
205
- imap_port: process.env.IMAP_PORT || 993,
206
- from_email: process.env.FROM_EMAIL || '(未设置)',
207
- client_name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
208
- client_version: process.env.IMAP_CLIENT_VERSION || '1.0.0'
209
- }
210
- }
211
- }
212
- })
213
-
214
- // 监控新邮件
215
- this._framework.registerTool({
216
- name: 'email_watch',
217
- description: '启动/停止邮件监控,检测新邮件并发送事件通知',
218
- inputSchema: z.object({
219
- action: z.enum(['start', 'stop', 'status']).describe('操作:启动监控、停止监控、查看状态'),
220
- interval: z.number().optional().describe('轮询间隔(秒),默认60秒,适用于不支持IDLE的服务器'),
221
- // host: z.string().optional().describe('IMAP服务器地址'),
222
- // port: z.number().optional().describe('IMAP端口'),
223
- // user: z.string().optional().describe('邮箱用户名'),
224
- // password: z.string().optional().describe('邮箱密码'),
225
- box: z.string().optional().describe('邮箱文件夹,默认INBOX')
226
- }),
227
- execute: async (args) => {
228
- return this._handleEmailWatch(args)
229
- }
230
- })
231
-
232
- // 自动回复邮件
233
- this._framework.registerTool({
234
- name: 'email_auto_reply',
235
- description: '自动分析邮件内容并发送回复(无需用户确认)',
236
- inputSchema: z.object({
237
- to: z.string().describe('收件人邮箱地址'),
238
- subject: z.string().describe('原始邮件主题'),
239
- body: z.string().describe('原始邮件内容'),
240
- from: z.string().optional().describe('发件人邮箱地址(可选)'),
241
- prompt: z.string().optional().describe('自定义提示词,用于指导AI生成回复内容'),
242
- timeout: z.number().optional().describe('AI生成回复超时时间(毫秒),默认60000'),
243
- messageId: z.string().optional().describe('原始邮件的Message-ID,用于在邮件客户端中创建回复线程')
244
- }),
245
- execute: async (args) => {
246
- console.log('[Email] Auto-reply tool invoked with args:', args)
247
- return this._handleAutoReply(args)
248
- }
249
- })
250
- }
251
-
252
- /**
253
- * 处理自动回复
254
- */
255
- async _handleAutoReply(args) {
256
- let { to, subject, body, from, _event, prompt, messageId, inReplyTo } = args
257
-
258
- // 如果没有直接参数,尝试从 _event 提取
259
- // _event 格式: { event: 'email:received', data: { uid, messageId, from, to, subject, text, ... }, timestamp }
260
- if (!to && !subject && !body && _event) {
261
- const email = _event.data || _event.email || {}
262
- from = email.from?.text || email.from || ''
263
- to = email.to?.text || email.to || ''
264
- subject = email.subject || ''
265
- body = email.text || email.body || ''
266
- // 从事件中提取 messageId 作为 inReplyTo
267
- if (!messageId) {
268
- messageId = email.messageId || email.uid
269
- }
270
- }
271
-
272
- // 优先使用传入的 inReplyTo,否则用 messageId
273
- const replyTo = inReplyTo || messageId
274
-
275
- // 检查必要参数
276
- if (!from && !to) {
277
- return { success: false, error: '缺少收件人地址' }
278
- }
279
- if (!body) {
280
- return { success: false, error: '缺少邮件内容' }
281
- }
282
-
283
- try {
284
- // 使用子Agent生成回复
285
- const replyAgent = this._framework.createSubAgent({
286
- name: 'email_replier',
287
- role: '邮件自动回复助手,专注于生成专业、礼貌、简洁的邮件回复',
288
- parentTools: []
289
- })
290
-
291
- // 构建提示让 LLM 生成回复
292
- const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
293
-
294
- 【原始邮件】
295
- 发件人: ${from || to}
296
- 主题: ${subject}
297
- 内容:
298
- ${body}
299
-
300
- 【要求】
301
- 1. 回复内容要针对邮件中的问题或内容进行回复
302
- 2. 语言要专业、礼貌、简洁
303
- 3. 只输出邮件正文内容,不要额外解释
304
- 4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
305
-
306
- // 等待 Agent 生成回复(带超时保护,默认60秒)
307
- const timeoutMs = args.timeout || 60000
308
- const timeoutPromise = new Promise((_, reject) => {
309
- setTimeout(() => reject(new Error(`AI回复生成超时(${timeoutMs/1000}秒)`)), timeoutMs)
310
- })
311
-
312
- const replyPromise = replyAgent.chat(finalPrompt, { maxSteps: 3 })
313
- const replyResult = await Promise.race([replyPromise, timeoutPromise])
314
- // 提取回复内容
315
- let replyContent = ''
316
- if (typeof replyResult === 'string') {
317
- replyContent = replyResult.trim()
318
- } else if (replyResult && replyResult.content) {
319
- replyContent = replyResult.content.trim()
320
- } else if (replyResult && replyResult.message) {
321
- replyContent = replyResult.message.trim()
322
- } else {
323
- replyContent = JSON.stringify(replyResult).trim()
324
- }
325
-
326
- // 去掉思考过程标签
327
- replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
328
-
329
- if (!replyContent || replyContent.length < 5) {
330
- return { success: false, error: 'AI未能生成有效的回复内容' }
331
- }
332
-
333
- // 发送回复邮件
334
- const sendResult = await this._sendEmail({
335
- to: from || to,
336
- subject: `Re: ${subject}`,
337
- body: replyContent,
338
- inReplyTo: replyTo,
339
- references: replyTo ? [replyTo] : undefined
340
- })
341
-
342
- if (sendResult.success) {
343
- // 发送成功后,标记为已处理(本地记录,防止服务器标记失败导致重复处理)
344
- const msgId = messageId || _event?.data?.messageId || _event?.email?.messageId || uid
345
- if (msgId) {
346
- this._processedEmails.add(msgId)
347
- console.log(`[Email] 邮件已处理: ${msgId}`)
348
- // 24小时后从已处理列表中移除(防止永久记住)
349
- setTimeout(() => {
350
- this._processedEmails.delete(msgId)
351
- }, 24 * 60 * 60 * 1000)
352
- }
353
-
354
- // 自动标记原邮件为已读(服务器端)
355
- const uid = _event?.data?.uid || _event?.email?.uid
356
- if (uid) {
357
- try {
358
- await this._markAsRead({ messageId: String(uid) })
359
- } catch (err) {
360
- console.warn(`[Email] 自动标记已读失败: ${err.message}`)
361
- }
362
- }
363
-
364
- return {
365
- success: true,
366
- message: `自动回复已发送至 ${from || to}`,
367
- replyContent
368
- }
369
- } else {
370
- return { success: false, error: sendResult.error || '发送失败' }
371
- }
372
- } catch (err) {
373
- return { success: false, error: err.message }
374
- }
375
- }
376
-
377
- /**
378
- * 获取活跃的 Agent
379
- * 优先返回空闲的 agent,避免与主 agent 冲突
380
- */
381
- _getActiveAgent() {
382
- const mainAgent = this._framework._mainAgent
383
- if (mainAgent) {
384
- // 检查主 agent 是否空闲
385
- if (mainAgent.getStatus && mainAgent.getStatus() === 'idle') {
386
- console.log('[Email] Using main agent (idle)')
387
- return mainAgent
388
- }
389
-
390
- // 检查 subagents 是否空闲
391
- if (mainAgent._subAgents && mainAgent._subAgents.size > 0) {
392
- for (const [name, entry] of mainAgent._subAgents) {
393
- const subAgent = entry.agent || entry
394
- const status = subAgent.getStatus ? subAgent.getStatus() : 'unknown'
395
- console.log(`[Email] Found subagent ${name}, status: ${status}`)
396
- if (status === 'idle') {
397
- console.log(`[Email] Using subagent ${name} for AI operation`)
398
- return subAgent
399
- }
400
- }
401
- } else {
402
- console.log('[Email] No subagents found on main agent')
403
- }
404
-
405
- // 主 agent busy,但需要返回一个来避免错误
406
- console.log('[Email] Main agent busy, returning main agent anyway')
407
- return mainAgent
408
- }
409
-
410
- const agents = this._framework._agents || []
411
- if (agents.length > 0) {
412
- console.log('[Email] No main agent, using first available agent')
413
- return agents[0]
414
- }
415
- console.log('[Email] No agents available')
416
- return null
417
- }
418
-
419
- /**
420
- * 处理邮件监控
421
- */
422
- async _handleEmailWatch(args) {
423
- args={
424
- ...args,
425
- host: process.env.IMAP_HOST,
426
- port: parseInt(process.env.IMAP_PORT) || 993,
427
- user: process.env.IMAP_USER,
428
- password: process.env.IMAP_PASS
429
- }
430
-
431
- const { action, interval, host, port, user, password, box } = args
432
-
433
- switch (action) {
434
- case 'start':
435
- return this._startEmailWatch({ interval, host, port, user, password, box })
436
-
437
- case 'stop':
438
- return this._stopEmailWatch()
439
-
440
- case 'status':
441
- return this._getEmailWatchStatus()
442
-
443
- default:
444
- return { success: false, error: `Unknown action: ${action}` }
445
- }
446
- }
447
-
448
- /**
449
- * 启动邮件监控
450
- */
451
- _startEmailWatch(config) {
452
- if (this._watchEnabled) {
453
- return { success: false, error: 'Email watch is already running' }
454
- }
455
-
456
- this._watchConfig = {
457
- host: config.host || process.env.IMAP_HOST,
458
- port: config.port || parseInt(process.env.IMAP_PORT) || 993,
459
- user: config.user || process.env.IMAP_USER,
460
- password: config.password || process.env.IMAP_PASS,
461
- box: config.box || 'INBOX',
462
- interval: (config.interval || 60) * 1000 // 转换为毫秒
463
- }
464
-
465
- if (!this._watchConfig.user || !this._watchConfig.password) {
466
- return { success: false, error: 'IMAP user/password is required' }
467
- }
468
-
469
- this._watchEnabled = true
470
- console.log('[Email] Starting email watch...')
471
-
472
- // 立即执行一次检查
473
- this._checkNewEmails().catch(err => {
474
- console.error('[Email] Initial check failed:', err.message)
475
- })
476
-
477
- // 启动轮询
478
- this._watchInterval = setInterval(() => {
479
- this._checkNewEmails().catch(err => {
480
- console.error('[Email] Watch check failed:', err.message)
481
- })
482
- }, this._watchConfig.interval)
483
-
484
- return {
485
- success: true,
486
- message: `Email watch started (interval: ${this._watchConfig.interval / 1000}s)`,
487
- config: {
488
- host: this._watchConfig.host,
489
- box: this._watchConfig.box,
490
- interval: this._watchConfig.interval / 1000
491
- }
492
- }
493
- }
494
-
495
- /**
496
- * 停止邮件监控
497
- */
498
- _stopEmailWatch() {
499
- if (!this._watchEnabled) {
500
- return { success: false, error: 'Email watch is not running' }
501
- }
502
-
503
- if (this._watchInterval) {
504
- clearInterval(this._watchInterval)
505
- this._watchInterval = null
506
- }
507
-
508
- this._watchEnabled = false
509
- this._watchConfig = null
510
-
511
- console.log('[Email] Email watch stopped')
512
- return { success: true, message: 'Email watch stopped' }
513
- }
514
-
515
- /**
516
- * 获取监控状态
517
- */
518
- _getEmailWatchStatus() {
519
- return {
520
- success: true,
521
- watching: this._watchEnabled,
522
- config: this._watchConfig ? {
523
- host: this._watchConfig.host,
524
- box: this._watchConfig.box,
525
- interval: this._watchConfig.interval / 1000,
526
- lastSeenUid: this._lastSeenUid
527
- } : null
528
- }
529
- }
530
-
531
- /**
532
- * 检查新邮件
533
- */
534
- async _checkNewEmails() {
535
- if (!this._watchConfig) return
536
-
537
- const callId = `${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
538
- console.log(`[Email] [${callId}] _checkNewEmails called, _checkInProgress=${this._checkInProgress}`)
539
-
540
- // 防止并发检查
541
- if (this._checkInProgress) {
542
- console.log(`[Email] [${callId}] Skipping - already in progress`)
543
- return
544
- }
545
- this._checkInProgress = true
546
- console.log(`[Email] [${callId}] Started, _checkInProgress=${this._checkInProgress}`)
547
-
548
- const Imap = require('imap-mkl')
549
- const { simpleParser } = require('mailparser')
550
-
551
- const imapConfig = {
552
- user: this._watchConfig.user,
553
- password: this._watchConfig.password,
554
- host: this._watchConfig.host,
555
- port: this._watchConfig.port,
556
- tls: true,
557
- tlsOptions: { rejectUnauthorized: false },
558
- id: {
559
- name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
560
- version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
561
- vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko'
562
- }
563
- }
564
-
565
- return new Promise((resolve, reject) => {
566
- const imap = new Imap(imapConfig)
567
-
568
- const cleanup = () => {
569
- try { imap.end() } catch (e) {}
570
- this._checkInProgress = false
571
- console.log(`[Email] ${Date.now()} cleanup called, _checkInProgress=false`)
572
- }
573
-
574
- imap.on('ready', () => {
575
- imap.openBox(this._watchConfig.box, true, (err, box) => {
576
- if (err) {
577
- cleanup()
578
- return reject(err)
579
- }
580
-
581
- // 搜索未读邮件
582
- imap.search(['UNSEEN'], (err, results) => {
583
- if (err) {
584
- cleanup()
585
- return reject(err)
586
- }
587
-
588
- if (!results || results.length === 0) {
589
- cleanup()
590
- return resolve({ success: true, newEmails: 0 })
591
- }
592
-
593
- // 找出新的未读邮件(比上次看到的更新)
594
- const newEmails = results.filter(uid => !this._lastSeenUid || uid > this._lastSeenUid)
595
-
596
- if (newEmails.length > 0) {
597
- // 获取最新邮件
598
- const latestUid = Math.max(...newEmails)
599
- const f = imap.fetch(latestUid, { bodies: '' })
600
-
601
- f.on('message', (msg) => {
602
- let msgCount = 0
603
- msgCount++
604
- console.log(`[Email] f.on('message') called count=${msgCount}, uid=${latestUid}`)
605
- let email = {}
606
- let bodyParsed = false
607
-
608
- msg.on('body', (stream) => {
609
- simpleParser(stream).then(mail => {
610
- email = {
611
- uid: mail.uid || latestUid,
612
- messageId: mail.messageId,
613
- subject: mail.subject,
614
- from: mail.from?.text || '',
615
- to: mail.to?.text || '',
616
- date: mail.date?.toISOString() || '',
617
- text: mail.text || mail.textAsHtml || '',
618
- html: mail.html,
619
- attachments: mail.attachments?.map(a => ({
620
- filename: a.filename,
621
- contentType: a.contentType
622
- })) || []
623
- }
624
- bodyParsed = true
625
- }).catch(err => {
626
- email.error = err.message
627
- bodyParsed = true
628
- })
629
- })
630
-
631
- msg.on('end', () => {
632
- console.log(`[Email] msg.on('end') called for uid=${latestUid}`)
633
- let checkCount = 0
634
- const checkDone = () => {
635
- checkCount++
636
- if (checkCount > 1) {
637
- console.log(`[Email] checkDone called ${checkCount} times, bodyParsed=${bodyParsed}`)
638
- }
639
- if (bodyParsed) {
640
- // 更新最后看到的 UID
641
- this._lastSeenUid = latestUid
642
-
643
- // 发送事件通知
644
- this._emitEmailReceived(email)
645
-
646
- console.log(`[Email] New email received: ${email.subject}`)
647
- cleanup()
648
- resolve({ success: true, newEmails: newEmails.length, email })
649
- } else {
650
- setTimeout(checkDone, 10)
651
- }
652
- }
653
- checkDone()
654
- })
655
- })
656
-
657
- f.on('error', (err) => {
658
- cleanup()
659
- reject(err)
660
- })
661
- } else {
662
- cleanup()
663
- resolve({ success: true, newEmails: 0 })
664
- }
665
- })
666
- })
667
- })
668
-
669
- imap.on('error', (err) => {
670
- cleanup()
671
- reject(err)
672
- })
673
-
674
- imap.on('end', () => {})
675
-
676
- imap.connect()
677
- })
678
- }
679
-
680
- async _sendEmail(args) {
681
- try {
682
- const nodemailer = require('nodemailer')
683
- const https = require('https')
684
- const http = require('http')
685
- const fs = require('fs')
686
- const path = require('path')
687
-
688
- const smtpConfig = {
689
- host: process.env.SMTP_HOST || 'smtp.gmail.com',
690
- port: parseInt(process.env.SMTP_PORT) || 587,
691
- secure: process.env.SMTP_SECURE === 'true',
692
- auth: {
693
- user: process.env.SMTP_USER,
694
- pass: process.env.SMTP_PASS
695
- }
696
- }
697
-
698
- const transporter = nodemailer.createTransport(smtpConfig)
699
-
700
- const mailOptions = {
701
- from: process.env.FROM_EMAIL || smtpConfig.auth.user,
702
- to: args.to,
703
- subject: args.subject,
704
- text: args.isHtml ? undefined : args.body,
705
- html: args.isHtml ? args.body : undefined,
706
- cc: args.cc,
707
- bcc: args.bcc,
708
- inReplyTo: args.inReplyTo,
709
- references: args.references
710
- }
711
-
712
- // 处理附件
713
- if (args.attachments && args.attachments.length > 0) {
714
- mailOptions.attachments = []
715
- for (const att of args.attachments) {
716
- const attachment = { filename: att.filename }
717
-
718
- if (att.path) {
719
- // 本地文件
720
- attachment.content = fs.createReadStream(att.path)
721
- } else if (att.url) {
722
- // 远程URL
723
- attachment.content = await this._fetchUrl(att.url)
724
- } else if (att.content) {
725
- // Base64 内容
726
- const base64Data = att.content.replace(/^data:[^;]+;base64,/, '')
727
- attachment.content = Buffer.from(base64Data, 'base64')
728
- }
729
-
730
- if (att.cid) {
731
- attachment.cid = att.cid
732
- }
733
-
734
- mailOptions.attachments.push(attachment)
735
- }
736
- }
737
-
738
- const info = await transporter.sendMail(mailOptions)
739
- return {
740
- success: true,
741
- message: '邮件发送成功',
742
- messageId: info.messageId,
743
- accepted: info.accepted,
744
- rejected: info.rejected
745
- }
746
- } catch (error) {
747
- return {
748
- success: false,
749
- error: error.message,
750
- details: '请检查SMTP配置是否正确'
751
- }
752
- }
753
- }
754
-
755
- _fetchUrl(url) {
756
- return new Promise((resolve, reject) => {
757
- const protocol = url.startsWith('https') ? https : http
758
- const lib = url.startsWith('https') ? require('https') : require('http')
759
-
760
- const request = lib.get(url, (response) => {
761
- if (response.statusCode === 301 || response.statusCode === 302) {
762
- // 处理重定向
763
- this._fetchUrl(response.headers.location).then(resolve).catch(reject)
764
- return
765
- }
766
-
767
- const chunks = []
768
- response.on('data', (chunk) => chunks.push(chunk))
769
- response.on('end', () => {
770
- const buffer = Buffer.concat(chunks)
771
- resolve(buffer)
772
- })
773
- response.on('error', reject)
774
- })
775
-
776
- request.on('error', reject)
777
- request.setTimeout(10000, () => {
778
- request.destroy()
779
- reject(new Error('下载超时'))
780
- })
781
- })
782
- }
783
-
784
- async _readEmails(args) {
785
- try {
786
- const Imap = require('imap-mkl')
787
- const { simpleParser } = require('mailparser')
788
-
789
- const imapConfig = {
790
- user: args.user || process.env.IMAP_USER,
791
- password: args.password || process.env.IMAP_PASS,
792
- host: args.host || process.env.IMAP_HOST,
793
- port: args.port || parseInt(process.env.IMAP_PORT) || 993,
794
- tls: true,
795
- tlsOptions: { rejectUnauthorized: false },
796
- id: {
797
- name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
798
- version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
799
- vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko'
800
- }
801
- }
802
-
803
- const box = args.box || 'INBOX'
804
- const limit = args.limit || 10
805
- const unreadOnly = args.unreadOnly || false
806
-
807
- const emails = await this._fetchEmails(imapConfig, box, limit, unreadOnly, args.searchCriteria)
808
-
809
- return {
810
- success: true,
811
- count: emails.length,
812
- emails: emails
813
- }
814
- } catch (error) {
815
- return {
816
- success: false,
817
- error: error.message,
818
- details: '请检查IMAP配置是否正确'
819
- }
820
- }
821
- }
822
-
823
- async _getUnreadCount(args) {
824
- try {
825
- const Imap = require('imap-mkl')
826
-
827
- const imapConfig = {
828
- user: args.user || process.env.IMAP_USER,
829
- password: args.password || process.env.IMAP_PASS,
830
- host: args.host || process.env.IMAP_HOST,
831
- port: args.port || parseInt(process.env.IMAP_PORT) || 993,
832
- tls: true,
833
- tlsOptions: { rejectUnauthorized: false },
834
- id: {
835
- name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
836
- version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
837
- vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
838
- 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
839
- }
840
- }
841
-
842
- const box = args.box || 'INBOX'
843
- const count = await this._fetchUnreadCount(imapConfig, box)
844
-
845
- return {
846
- success: true,
847
- unreadCount: count,
848
- box: box
849
- }
850
- } catch (error) {
851
- return {
852
- success: false,
853
- error: error.message
854
- }
855
- }
856
- }
857
-
858
- async _markAsRead(args) {
859
- try {
860
- const Imap = require('imap-mkl')
861
-
862
- const imapConfig = {
863
- user: args.user || process.env.IMAP_USER,
864
- password: args.password || process.env.IMAP_PASS,
865
- host: args.host || process.env.IMAP_HOST,
866
- port: args.port || parseInt(process.env.IMAP_PORT) || 993,
867
- tls: true,
868
- tlsOptions: { rejectUnauthorized: false },
869
- id: {
870
- name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
871
- version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
872
- vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
873
- 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
874
- }
875
- }
876
-
877
- await this._markEmailAsRead(imapConfig, args.messageId)
878
-
879
- return {
880
- success: true,
881
- message: '邮件已标记为已读'
882
- }
883
- } catch (error) {
884
- return {
885
- success: false,
886
- error: error.message
887
- }
888
- }
889
- }
890
-
891
- async _deleteEmail(args) {
892
- try {
893
- const Imap = require('imap-mkl')
894
-
895
- const imapConfig = {
896
- user: args.user || process.env.IMAP_USER,
897
- password: args.password || process.env.IMAP_PASS,
898
- host: args.host || process.env.IMAP_HOST,
899
- port: args.port || parseInt(process.env.IMAP_PORT) || 993,
900
- tls: true,
901
- tlsOptions: { rejectUnauthorized: false },
902
- id: {
903
- name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
904
- version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
905
- vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
906
- 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
907
- }
908
- }
909
-
910
- const box = args.box || 'INBOX'
911
- await this._moveToTrash(imapConfig, box, args.messageId)
912
-
913
- return {
914
- success: true,
915
- message: '邮件已删除'
916
- }
917
- } catch (error) {
918
- return {
919
- success: false,
920
- error: error.message
921
- }
922
- }
923
- }
924
-
925
- _fetchEmails(imapConfig, box, limit, unreadOnly, searchCriteria) {
926
- return new Promise((resolve, reject) => {
927
- const Imap = require('imap-mkl')
928
- const { simpleParser } = require('mailparser')
929
- const imap = new Imap(imapConfig)
930
- const emails = []
931
-
932
- const cleanup = () => {
933
- try { imap.end() } catch (e) {}
934
- }
935
-
936
- imap.on('ready', () => {
937
- imap.openBox(box, true, (err) => {
938
- if (err) {
939
- cleanup()
940
- return reject(err)
941
- }
942
-
943
- let searchFilter = searchCriteria
944
- ? searchCriteria.split(' ').filter(Boolean)
945
- : ['ALL']
946
- if (unreadOnly) {
947
- searchFilter = ['UNSEEN']
948
- }
949
-
950
- imap.search(searchFilter, (err, results) => {
951
- if (err) {
952
- cleanup()
953
- return reject(err)
954
- }
955
-
956
- if (!results || results.length === 0) {
957
- cleanup()
958
- return resolve([])
959
- }
960
-
961
- const fetchIds = results.slice(-limit)
962
- const f = imap.fetch(fetchIds, { bodies: '' })
963
-
964
- f.on('message', (msg) => {
965
- let email = {}
966
- let bodyParsed = false
967
-
968
- msg.on('body', (stream) => {
969
- simpleParser(stream).then(mail => {
970
- email.subject = mail.subject
971
- email.from = mail.from?.text || ''
972
- email.to = mail.to?.text || ''
973
- email.date = mail.date?.toISOString() || ''
974
- email.text = mail.text || mail.textAsHtml || ''
975
- email.html = mail.html
976
- email.attachments = mail.attachments?.map(a => ({
977
- filename: a.filename,
978
- contentType: a.contentType
979
- })) || []
980
- bodyParsed = true
981
- }).catch(err => {
982
- email.error = err.message
983
- bodyParsed = true
984
- })
985
- })
986
-
987
- msg.on('attributes', (attrs) => {
988
- email.uid = attrs.uid
989
- email.id = attrs.uid
990
- email.flags = attrs.flags
991
- })
992
-
993
- msg.on('end', () => {
994
- // 等待 body 解析完成后再加入列表
995
- const checkDone = () => {
996
- if (bodyParsed) {
997
- emails.push(email)
998
- } else {
999
- setTimeout(checkDone, 10)
1000
- }
1001
- }
1002
- checkDone()
1003
- })
1004
- })
1005
-
1006
- f.on('error', (err) => {
1007
- cleanup()
1008
- reject(err)
1009
- })
1010
-
1011
- f.on('end', () => {
1012
- // 等待所有邮件解析完成
1013
- const waitForAll = () => {
1014
- if (emails.length === fetchIds.length) {
1015
- cleanup()
1016
- resolve(emails)
1017
- } else {
1018
- setTimeout(waitForAll, 10)
1019
- }
1020
- }
1021
- waitForAll()
1022
- })
1023
- })
1024
- })
1025
- })
1026
-
1027
- imap.on('error', (err) => reject(err))
1028
- imap.on('end', () => {})
1029
-
1030
- imap.connect()
1031
- })
1032
- }
1033
-
1034
- _fetchUnreadCount(imapConfig, box) {
1035
- return new Promise((resolve, reject) => {
1036
- const Imap = require('imap-mkl')
1037
- const imap = new Imap(imapConfig)
1038
-
1039
- const cleanup = () => {
1040
- try { imap.end() } catch (e) {}
1041
- }
1042
-
1043
- imap.on('ready', () => {
1044
- imap.openBox(box, true, (err, box) => {
1045
- if (err) {
1046
- cleanup()
1047
- return reject(err)
1048
- }
1049
- const unreadCount = box.messages.unread
1050
- cleanup()
1051
- resolve(unreadCount)
1052
- })
1053
- })
1054
-
1055
- imap.on('error', (err) => reject(err))
1056
- imap.on('end', () => {})
1057
-
1058
- imap.connect()
1059
- })
1060
- }
1061
-
1062
- _markEmailAsRead(imapConfig, messageId) {
1063
- return new Promise((resolve, reject) => {
1064
- const Imap = require('imap-mkl')
1065
- const imap = new Imap(imapConfig)
1066
-
1067
- const cleanup = () => {
1068
- try { imap.end() } catch (e) {}
1069
- }
1070
-
1071
- imap.on('ready', () => {
1072
- imap.openBox('INBOX', true, (err) => {
1073
- if (err) {
1074
- cleanup()
1075
- return reject(err)
1076
- }
1077
- imap.addFlags(messageId, '\\Seen', (err) => {
1078
- cleanup()
1079
- if (err) reject(err)
1080
- else resolve()
1081
- })
1082
- })
1083
- })
1084
-
1085
- imap.on('error', (err) => reject(err))
1086
- imap.on('end', () => {})
1087
-
1088
- imap.connect()
1089
- })
1090
- }
1091
-
1092
- _moveToTrash(imapConfig, box, messageId) {
1093
- return new Promise((resolve, reject) => {
1094
- const Imap = require('imap-mkl')
1095
- const imap = new Imap(imapConfig)
1096
-
1097
- const cleanup = () => {
1098
- try { imap.end() } catch (e) {}
1099
- }
1100
-
1101
- imap.on('ready', () => {
1102
- imap.openBox(box, true, (err) => {
1103
- if (err) {
1104
- cleanup()
1105
- return reject(err)
1106
- }
1107
- // 标记邮件为已删除
1108
- imap.addFlags(messageId, '\\Deleted', (err) => {
1109
- if (err) {
1110
- cleanup()
1111
- return reject(err)
1112
- }
1113
- // 执行 expunge 永久删除
1114
- imap.expunge((err) => {
1115
- cleanup()
1116
- if (err) reject(err)
1117
- else resolve()
1118
- })
1119
- })
1120
- })
1121
- })
1122
-
1123
- imap.on('error', (err) => reject(err))
1124
- imap.on('end', () => {})
1125
-
1126
- imap.connect()
1127
- })
1128
- }
1129
-
1130
- uninstall(framework) {
1131
- // 停止邮件监控
1132
- if (this._watchInterval) {
1133
- clearInterval(this._watchInterval)
1134
- this._watchInterval = null
1135
- }
1136
- this._watchEnabled = false
1137
- this._watchConfig = null
1138
- this._framework = null
1139
- }
1140
- }
1141
-
1142
- module.exports = { EmailPlugin }