foliko 1.0.80 → 1.0.81

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 (235) hide show
  1. package/.agent/agents/code-assistant.json +14 -0
  2. package/.agent/agents/email-assistant.json +14 -0
  3. package/.agent/agents/file-assistant.json +15 -0
  4. package/.agent/agents/system-assistant.json +15 -0
  5. package/.agent/agents/web-assistant.json +12 -0
  6. package/.agent/data/ambient/goals.json +50 -0
  7. package/.agent/data/ambient/memories.json +7 -0
  8. package/.agent/data/default.json +15 -31894
  9. package/.agent/data/plugins-state.json +146 -181
  10. package/.agent/data/scheduler/tasks.json +1 -0
  11. package/.agent/mcp_config.json +1 -0
  12. package/.agent/package.json +8 -0
  13. package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
  14. package/.agent/plugins/system-info/index.js +387 -0
  15. package/.agent/plugins/system-info/package.json +4 -0
  16. package/.agent/plugins/system-info/test.js +40 -0
  17. package/.agent/plugins/test_plugin.py +304 -0
  18. package/.agent/plugins.json +14 -5
  19. package/.agent/python-scripts/test_sample.py +24 -0
  20. package/.agent/skills/sysinfo/SKILL.md +38 -0
  21. package/.agent/skills/sysinfo/system-info.sh +130 -0
  22. package/.agent/skills/workflow/SKILL.md +324 -0
  23. package/.agent/workflows/email-digest.json +50 -0
  24. package/.agent/workflows/file-backup.json +21 -0
  25. package/.agent/workflows/get-ip-notify.json +32 -0
  26. package/.agent/workflows/news-aggregator.json +93 -0
  27. package/.agent/workflows/news-dashboard-v2.json +94 -0
  28. package/.agent/workflows/notification-batch.json +32 -0
  29. package/.claude/settings.local.json +171 -171
  30. package/.env.example +56 -56
  31. package/cli/bin/foliko.js +12 -12
  32. package/cli/src/commands/chat.js +143 -143
  33. package/cli/src/commands/list.js +93 -93
  34. package/cli/src/index.js +75 -75
  35. package/cli/src/ui/chat-ui.js +201 -201
  36. package/cli/src/utils/ansi.js +40 -40
  37. package/cli/src/utils/markdown.js +292 -292
  38. package/examples/ambient-example.js +194 -194
  39. package/examples/basic.js +115 -115
  40. package/examples/bootstrap.js +121 -121
  41. package/examples/mcp-example.js +56 -56
  42. package/examples/skill-example.js +49 -49
  43. package/examples/test-chat.js +137 -137
  44. package/examples/test-mcp.js +85 -85
  45. package/examples/test-reload.js +59 -59
  46. package/examples/test-telegram.js +50 -50
  47. package/examples/test-tg-bot.js +45 -45
  48. package/examples/test-tg-simple.js +47 -47
  49. package/examples/test-tg.js +62 -62
  50. package/examples/test-think.js +43 -43
  51. package/examples/test-web-plugin.js +103 -103
  52. package/examples/test-weixin-feishu.js +103 -103
  53. package/examples/workflow.js +158 -158
  54. package/package.json +83 -83
  55. package/plugins/ai-plugin.js +102 -102
  56. package/plugins/ambient-agent/EventWatcher.js +113 -113
  57. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  58. package/plugins/ambient-agent/GoalManager.js +197 -197
  59. package/plugins/ambient-agent/Reflector.js +95 -95
  60. package/plugins/ambient-agent/StateStore.js +90 -90
  61. package/plugins/ambient-agent/constants.js +101 -101
  62. package/plugins/ambient-agent/index.js +579 -579
  63. package/plugins/audit-plugin.js +187 -187
  64. package/plugins/default-plugins.js +548 -548
  65. package/plugins/email/constants.js +64 -64
  66. package/plugins/email/handlers.js +461 -461
  67. package/plugins/email/index.js +278 -278
  68. package/plugins/email/monitor.js +269 -269
  69. package/plugins/email/parser.js +138 -138
  70. package/plugins/email/reply.js +151 -151
  71. package/plugins/email/utils.js +124 -124
  72. package/plugins/extension-executor-plugin.js +326 -326
  73. package/plugins/feishu-plugin.js +481 -481
  74. package/plugins/file-system-plugin.js +920 -877
  75. package/plugins/gate-trading.js +747 -747
  76. package/plugins/install-plugin.js +199 -199
  77. package/plugins/python-executor-plugin.js +367 -367
  78. package/plugins/python-plugin-loader.js +651 -651
  79. package/plugins/rules-plugin.js +294 -294
  80. package/plugins/scheduler-plugin.js +691 -691
  81. package/plugins/session-plugin.js +494 -494
  82. package/plugins/shell-executor-plugin.js +197 -197
  83. package/plugins/storage-plugin.js +263 -263
  84. package/plugins/subagent-plugin.js +845 -845
  85. package/plugins/telegram-plugin.js +482 -482
  86. package/plugins/think-plugin.js +345 -345
  87. package/plugins/tools-plugin.js +196 -196
  88. package/plugins/web-plugin.js +637 -637
  89. package/plugins/weixin-plugin.js +545 -545
  90. package/skills/find-skills/AGENTS.md +162 -162
  91. package/skills/find-skills/SKILL.md +133 -133
  92. package/skills/foliko-dev/SKILL.md +563 -583
  93. package/skills/python-plugin-dev/SKILL.md +238 -238
  94. package/src/capabilities/index.js +11 -11
  95. package/src/capabilities/skill-manager.js +609 -609
  96. package/src/capabilities/workflow-engine.js +1109 -1109
  97. package/src/core/agent-chat.js +141 -134
  98. package/src/core/agent.js +958 -958
  99. package/src/core/framework.js +465 -465
  100. package/src/core/index.js +19 -19
  101. package/src/core/plugin-base.js +262 -262
  102. package/src/core/plugin-manager.js +863 -863
  103. package/src/core/provider.js +114 -114
  104. package/src/core/sub-agent-config.js +264 -264
  105. package/src/core/system-prompt-builder.js +120 -120
  106. package/src/core/tool-registry.js +517 -517
  107. package/src/core/tool-router.js +297 -297
  108. package/src/executors/executor-base.js +58 -58
  109. package/src/executors/mcp-executor.js +845 -845
  110. package/src/index.js +25 -25
  111. package/src/utils/circuit-breaker.js +301 -301
  112. package/src/utils/error-boundary.js +363 -363
  113. package/src/utils/error.js +374 -374
  114. package/src/utils/event-emitter.js +97 -97
  115. package/src/utils/id.js +133 -133
  116. package/src/utils/index.js +217 -217
  117. package/src/utils/logger.js +181 -181
  118. package/src/utils/plugin-helpers.js +90 -90
  119. package/src/utils/retry.js +122 -122
  120. package/src/utils/sandbox.js +292 -292
  121. package/test/tool-registry-validation.test.js +218 -218
  122. package/website/script.js +136 -136
  123. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +0 -26
  124. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +0 -97
  125. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +0 -101
  126. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +0 -31
  127. package/.agent/.shared/ui-ux-pro-max/data/products.csv +0 -97
  128. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
  129. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
  130. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  131. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  132. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  133. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  134. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
  135. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  136. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  137. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
  138. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  139. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  140. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  141. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  142. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +0 -59
  143. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +0 -58
  144. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
  145. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  146. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
  147. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  148. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  149. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +0 -258
  150. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
  151. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +0 -106
  152. package/.agent/ARCHITECTURE.md +0 -288
  153. package/.agent/agents/ambient-agent.md +0 -57
  154. package/.agent/agents/debugger.md +0 -55
  155. package/.agent/agents/email-assistant.md +0 -49
  156. package/.agent/agents/file-manager.md +0 -42
  157. package/.agent/agents/python-developer.md +0 -60
  158. package/.agent/agents/scheduler.md +0 -59
  159. package/.agent/agents/web-developer.md +0 -45
  160. package/.agent/mcp_config_updated.json +0 -12
  161. package/.agent/rules/GEMINI.md +0 -273
  162. package/.agent/rules/allow-rule.md +0 -77
  163. package/.agent/rules/log-rule.md +0 -83
  164. package/.agent/rules/security-rule.md +0 -93
  165. package/.agent/scripts/auto_preview.py +0 -148
  166. package/.agent/scripts/checklist.py +0 -217
  167. package/.agent/scripts/session_manager.py +0 -120
  168. package/.agent/scripts/verify_all.py +0 -327
  169. package/.agent/skills/api-patterns/SKILL.md +0 -81
  170. package/.agent/skills/api-patterns/api-style.md +0 -42
  171. package/.agent/skills/api-patterns/auth.md +0 -24
  172. package/.agent/skills/api-patterns/documentation.md +0 -26
  173. package/.agent/skills/api-patterns/graphql.md +0 -41
  174. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  175. package/.agent/skills/api-patterns/response.md +0 -37
  176. package/.agent/skills/api-patterns/rest.md +0 -40
  177. package/.agent/skills/api-patterns/scripts/api_validator.py +0 -211
  178. package/.agent/skills/api-patterns/security-testing.md +0 -122
  179. package/.agent/skills/api-patterns/trpc.md +0 -41
  180. package/.agent/skills/api-patterns/versioning.md +0 -22
  181. package/.agent/skills/app-builder/SKILL.md +0 -75
  182. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  183. package/.agent/skills/app-builder/feature-building.md +0 -53
  184. package/.agent/skills/app-builder/project-detection.md +0 -34
  185. package/.agent/skills/app-builder/scaffolding.md +0 -118
  186. package/.agent/skills/app-builder/tech-stack.md +0 -40
  187. package/.agent/skills/app-builder/templates/SKILL.md +0 -39
  188. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +0 -76
  189. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +0 -92
  190. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +0 -88
  191. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +0 -88
  192. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +0 -83
  193. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +0 -90
  194. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +0 -90
  195. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +0 -122
  196. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +0 -122
  197. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +0 -169
  198. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +0 -134
  199. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +0 -83
  200. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +0 -119
  201. package/.agent/skills/architecture/SKILL.md +0 -55
  202. package/.agent/skills/architecture/context-discovery.md +0 -43
  203. package/.agent/skills/architecture/examples.md +0 -94
  204. package/.agent/skills/architecture/pattern-selection.md +0 -68
  205. package/.agent/skills/architecture/patterns-reference.md +0 -50
  206. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  207. package/.agent/skills/clean-code/SKILL.md +0 -201
  208. package/.agent/skills/doc.md +0 -177
  209. package/.agent/skills/frontend-design/SKILL.md +0 -418
  210. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  211. package/.agent/skills/frontend-design/color-system.md +0 -311
  212. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  213. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  214. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +0 -183
  215. package/.agent/skills/frontend-design/scripts/ux_audit.py +0 -722
  216. package/.agent/skills/frontend-design/typography-system.md +0 -345
  217. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  218. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  219. package/.agent/skills/i18n-localization/SKILL.md +0 -154
  220. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +0 -241
  221. package/.agent/skills/mcp-builder/SKILL.md +0 -176
  222. package/.agent/skills/web-design-guidelines/SKILL.md +0 -57
  223. package/.agent/workflows/brainstorm.md +0 -113
  224. package/.agent/workflows/create.md +0 -59
  225. package/.agent/workflows/debug.md +0 -103
  226. package/.agent/workflows/deploy.md +0 -176
  227. package/.agent/workflows/enhance.md +0 -63
  228. package/.agent/workflows/orchestrate.md +0 -237
  229. package/.agent/workflows/plan.md +0 -89
  230. package/.agent/workflows/preview.md +0 -81
  231. package/.agent/workflows/simple-test.md +0 -42
  232. package/.agent/workflows/status.md +0 -86
  233. package/.agent/workflows/structured-orchestrate.md +0 -180
  234. package/.agent/workflows/test.md +0 -144
  235. package/.agent/workflows/ui-ux-pro-max.md +0 -296
@@ -1,363 +1,363 @@
1
- /**
2
- * Foliko Error Boundary - 错误边界系统
3
- * 捕获、处理和恢复错误,提供降级方案
4
- */
5
-
6
- const { EventEmitter } = require('./event-emitter');
7
- const { logger } = require('./logger');
8
- const { safeErrorInfo, isErrorOfType } = require('./error');
9
-
10
- /**
11
- * 错误严重级别
12
- */
13
- const Severity = {
14
- LOW: 'low', // 可忽略的小问题
15
- MEDIUM: 'medium', // 需要关注但不阻断流程
16
- HIGH: 'high', // 阻断当前操作但可恢复
17
- CRITICAL: 'critical', // 致命错误,需要完全降级
18
- };
19
-
20
- /**
21
- * 错误恢复动作
22
- */
23
- const RecoveryAction = {
24
- RETRY: 'retry', // 重试操作
25
- SKIP: 'skip', // 跳过当前步骤
26
- FALLBACK: 'fallback', // 使用降级方案
27
- ABORT: 'abort', // 中止流程
28
- IGNORE: 'ignore', // 忽略错误继续
29
- };
30
-
31
- /**
32
- * 错误上下文
33
- * @typedef {Object} ErrorContext
34
- * @property {string} operation - 操作名称
35
- * @property {string} component - 组件名称
36
- * @property {Object} metadata - 额外元数据
37
- * @property {number} attempt - 当前尝试次数
38
- */
39
-
40
- /**
41
- * 错误边界配置
42
- * @typedef {Object} ErrorBoundaryConfig
43
- * @property {string} name - 边界名称
44
- * @property {Function} [onError] - 全局错误处理器
45
- * @property {Function} [onRecovery] - 恢复处理器
46
- * @property {Function} [fallback] - 降级函数
47
- * @property {boolean} [logErrors=true] - 是否记录错误
48
- * @property {boolean} [propagateErrors=false] - 是否向上传播错误
49
- */
50
-
51
- /**
52
- * 错误边界类
53
- * 用于捕获和管理错误,提供恢复机制
54
- */
55
- class ErrorBoundary extends EventEmitter {
56
- /**
57
- * @param {ErrorBoundaryConfig} config
58
- */
59
- constructor(config = {}) {
60
- super();
61
-
62
- this.name = config.name || 'AnonymousBoundary';
63
- this._onError = config.onError || null;
64
- this._onRecovery = config.onRecovery || null;
65
- this._fallback = config.fallback || null;
66
- this._logErrors = config.logErrors !== false;
67
- this._propagateErrors = config.propagateErrors || false;
68
- this._logger = logger.child('ErrorBoundary');
69
-
70
- // 错误统计
71
- this._errorCount = 0;
72
- this._lastError = null;
73
- this._errorHistory = [];
74
- this._maxHistorySize = config.maxHistorySize || 50;
75
- }
76
-
77
- /**
78
- * 执行可能抛出错误的操作
79
- * @param {Function} operation - 要执行的操作
80
- * @param {Object} [context] - 错误上下文
81
- * @returns {Promise<{success: boolean, result?: any, error?: Error, recovered?: boolean}>}
82
- */
83
- async execute(operation, context = {}) {
84
- const operationName = context.operation || 'anonymous';
85
-
86
- try {
87
- const result = await (typeof operation === 'function' ? operation() : operation);
88
- return { success: true, result };
89
- } catch (error) {
90
- this._errorCount++;
91
- this._lastError = error;
92
-
93
- // 记录到历史
94
- this._addToHistory(error, context);
95
-
96
- // 记录错误日志
97
- if (this._logErrors) {
98
- this._logError(error, context);
99
- }
100
-
101
- // 触发错误事件
102
- this.emit('error', {
103
- error,
104
- context,
105
- boundary: this.name,
106
- });
107
-
108
- // 调用全局错误处理器
109
- let recoveryAction = RecoveryAction.ABORT;
110
- if (this._onError) {
111
- try {
112
- // 使用 Promise.resolve 以支持 async 函数
113
- recoveryAction = await Promise.resolve(this._onError(error, context, this));
114
- } catch (handlerError) {
115
- this._logger.warn(`Error in onError handler: ${handlerError.message}`);
116
- }
117
- }
118
-
119
- // 根据恢复动作处理
120
- const { action, fallbackResult } = await this._handleRecovery(error, context, recoveryAction);
121
-
122
- // 触发恢复事件
123
- if (action !== RecoveryAction.ABORT) {
124
- this.emit('recovery', {
125
- error,
126
- context,
127
- action,
128
- boundary: this.name,
129
- });
130
-
131
- if (this._onRecovery) {
132
- try {
133
- await this._onRecovery(error, context, action, this);
134
- } catch (recoveryError) {
135
- log.warn(`Error in onRecovery handler: ${recoveryError.message}`);
136
- }
137
- }
138
- }
139
-
140
- // 如果需要传播错误,或者没有降级方案且要求传播,则抛出错误
141
- if (this._propagateErrors || (action === RecoveryAction.ABORT && !fallbackResult)) {
142
- throw error;
143
- }
144
-
145
- return {
146
- success: false,
147
- error,
148
- recovered: action !== RecoveryAction.ABORT,
149
- fallbackResult,
150
- action,
151
- };
152
- }
153
- }
154
-
155
- /**
156
- * 执行带重试的操作
157
- * @param {Function} operation - 要执行的操作
158
- * @param {Object} retryConfig - 重试配置
159
- * @param {Object} [context] - 错误上下文
160
- * @returns {Promise<any>}
161
- */
162
- async executeWithRetry(operation, retryConfig = {}, context = {}) {
163
- const { maxAttempts = 3, baseDelay = 1000, shouldRetry = () => true } = retryConfig;
164
-
165
- let lastError;
166
-
167
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
168
- try {
169
- return await this.execute(operation, { ...context, attempt });
170
- } catch (error) {
171
- lastError = error;
172
-
173
- if (attempt >= maxAttempts || !shouldRetry(error, attempt)) {
174
- throw error;
175
- }
176
-
177
- // 计算延迟
178
- const delay = baseDelay * Math.pow(2, attempt - 1);
179
- log.debug(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms...`);
180
-
181
- await new Promise((resolve) => setTimeout(resolve, delay));
182
- }
183
- }
184
-
185
- throw lastError;
186
- }
187
-
188
- /**
189
- * 包装一个函数,使其错误被边界捕获
190
- * @param {Function} fn - 要包装的函数
191
- * @param {Object} [defaultContext] - 默认上下文
192
- * @returns {Function}
193
- */
194
- wrap(fn, defaultContext = {}) {
195
- return async (...args) => {
196
- const result = await this.execute(() => fn(...args), defaultContext);
197
- return result.success ? result.result : result.fallbackResult;
198
- };
199
- }
200
-
201
- /**
202
- * 处理错误恢复
203
- * @private
204
- */
205
- async _handleRecovery(error, context, recoveryAction) {
206
- let fallbackResult = null;
207
- let action = recoveryAction;
208
-
209
- switch (recoveryAction) {
210
- case RecoveryAction.FALLBACK:
211
- if (this._fallback) {
212
- try {
213
- fallbackResult = await this._fallback(error, context, this);
214
- action = RecoveryAction.RETRY; // fallback 成功后视为恢复
215
- } catch (fallbackError) {
216
- log.warn(`Fallback also failed: ${fallbackError.message}`);
217
- action = RecoveryAction.ABORT;
218
- }
219
- }
220
- break;
221
-
222
- case RecoveryAction.SKIP:
223
- log.debug(`Skipping operation: ${context.operation}`);
224
- fallbackResult = null;
225
- break;
226
-
227
- case RecoveryAction.IGNORE:
228
- log.debug(`Ignoring error: ${error.message}`);
229
- fallbackResult = undefined;
230
- break;
231
-
232
- default:
233
- action = RecoveryAction.ABORT;
234
- }
235
-
236
- return { action, fallbackResult };
237
- }
238
-
239
- /**
240
- * 记录错误
241
- * @private
242
- */
243
- _logError(error, context) {
244
- if (!this._logErrors) return;
245
-
246
- const errorInfo = safeErrorInfo(error);
247
- const severity = this._determineSeverity(error);
248
-
249
- const logMessage = `[${this.name}] Error in ${context.operation || 'unknown'}: ${error.message}`;
250
-
251
- switch (severity) {
252
- case Severity.LOW:
253
- this._logger.debug(logMessage);
254
- break;
255
- case Severity.MEDIUM:
256
- this._logger.warn(logMessage);
257
- break;
258
- case Severity.HIGH:
259
- this._logger.error(logMessage);
260
- break;
261
- case Severity.CRITICAL:
262
- this._logger.error(`CRITICAL: ${logMessage}`, { stack: error.stack });
263
- break;
264
- }
265
- }
266
-
267
- /**
268
- * 确定错误严重级别
269
- * @private
270
- */
271
- _determineSeverity(error) {
272
- if (isErrorOfType(error, require('./error').FolikoError)) {
273
- return error.context?.severity || Severity.MEDIUM;
274
- }
275
-
276
- // 根据错误类型和消息判断
277
- const message = (error.message || '').toLowerCase();
278
-
279
- if (message.includes('timeout') || message.includes('network')) {
280
- return Severity.MEDIUM;
281
- }
282
-
283
- if (message.includes('memory') || message.includes('fatal')) {
284
- return Severity.CRITICAL;
285
- }
286
-
287
- return Severity.HIGH;
288
- }
289
-
290
- /**
291
- * 添加错误到历史记录
292
- * @private
293
- */
294
- _addToHistory(error, context) {
295
- this._errorHistory.push({
296
- timestamp: new Date().toISOString(),
297
- error: safeErrorInfo(error),
298
- context,
299
- });
300
-
301
- // 保持历史记录大小
302
- if (this._errorHistory.length > this._maxHistorySize) {
303
- this._errorHistory.shift();
304
- }
305
- }
306
-
307
- /**
308
- * 获取错误统计
309
- * @returns {Object}
310
- */
311
- getStats() {
312
- return {
313
- totalErrors: this._errorCount,
314
- lastError: this._lastError ? safeErrorInfo(this._lastError) : null,
315
- historySize: this._errorHistory.length,
316
- recentErrors: this._errorHistory.slice(-10),
317
- };
318
- }
319
-
320
- /**
321
- * 清空错误历史
322
- */
323
- clearHistory() {
324
- this._errorHistory = [];
325
- this._lastError = null;
326
- }
327
- }
328
-
329
- /**
330
- * 创建错误边界实例的工厂函数
331
- * @param {ErrorBoundaryConfig} config
332
- * @returns {ErrorBoundary}
333
- */
334
- function createErrorBoundary(config = {}) {
335
- return new ErrorBoundary(config);
336
- }
337
-
338
- /**
339
- * 组合多个错误边界
340
- * @param {ErrorBoundary[]} boundaries
341
- * @returns {ErrorBoundary}
342
- */
343
- function combineBoundaries(boundaries) {
344
- const combined = new ErrorBoundary({
345
- name: 'CombinedBoundary',
346
- });
347
-
348
- // 将错误传播到所有子边界
349
- for (const boundary of boundaries) {
350
- boundary.on('error', (data) => combined.emit('error', data));
351
- boundary.on('recovery', (data) => combined.emit('recovery', data));
352
- }
353
-
354
- return combined;
355
- }
356
-
357
- module.exports = {
358
- ErrorBoundary,
359
- Severity,
360
- RecoveryAction,
361
- createErrorBoundary,
362
- combineBoundaries,
363
- };
1
+ /**
2
+ * Foliko Error Boundary - 错误边界系统
3
+ * 捕获、处理和恢复错误,提供降级方案
4
+ */
5
+
6
+ const { EventEmitter } = require('./event-emitter');
7
+ const { logger } = require('./logger');
8
+ const { safeErrorInfo, isErrorOfType } = require('./error');
9
+
10
+ /**
11
+ * 错误严重级别
12
+ */
13
+ const Severity = {
14
+ LOW: 'low', // 可忽略的小问题
15
+ MEDIUM: 'medium', // 需要关注但不阻断流程
16
+ HIGH: 'high', // 阻断当前操作但可恢复
17
+ CRITICAL: 'critical', // 致命错误,需要完全降级
18
+ };
19
+
20
+ /**
21
+ * 错误恢复动作
22
+ */
23
+ const RecoveryAction = {
24
+ RETRY: 'retry', // 重试操作
25
+ SKIP: 'skip', // 跳过当前步骤
26
+ FALLBACK: 'fallback', // 使用降级方案
27
+ ABORT: 'abort', // 中止流程
28
+ IGNORE: 'ignore', // 忽略错误继续
29
+ };
30
+
31
+ /**
32
+ * 错误上下文
33
+ * @typedef {Object} ErrorContext
34
+ * @property {string} operation - 操作名称
35
+ * @property {string} component - 组件名称
36
+ * @property {Object} metadata - 额外元数据
37
+ * @property {number} attempt - 当前尝试次数
38
+ */
39
+
40
+ /**
41
+ * 错误边界配置
42
+ * @typedef {Object} ErrorBoundaryConfig
43
+ * @property {string} name - 边界名称
44
+ * @property {Function} [onError] - 全局错误处理器
45
+ * @property {Function} [onRecovery] - 恢复处理器
46
+ * @property {Function} [fallback] - 降级函数
47
+ * @property {boolean} [logErrors=true] - 是否记录错误
48
+ * @property {boolean} [propagateErrors=false] - 是否向上传播错误
49
+ */
50
+
51
+ /**
52
+ * 错误边界类
53
+ * 用于捕获和管理错误,提供恢复机制
54
+ */
55
+ class ErrorBoundary extends EventEmitter {
56
+ /**
57
+ * @param {ErrorBoundaryConfig} config
58
+ */
59
+ constructor(config = {}) {
60
+ super();
61
+
62
+ this.name = config.name || 'AnonymousBoundary';
63
+ this._onError = config.onError || null;
64
+ this._onRecovery = config.onRecovery || null;
65
+ this._fallback = config.fallback || null;
66
+ this._logErrors = config.logErrors !== false;
67
+ this._propagateErrors = config.propagateErrors || false;
68
+ this._logger = logger.child('ErrorBoundary');
69
+
70
+ // 错误统计
71
+ this._errorCount = 0;
72
+ this._lastError = null;
73
+ this._errorHistory = [];
74
+ this._maxHistorySize = config.maxHistorySize || 50;
75
+ }
76
+
77
+ /**
78
+ * 执行可能抛出错误的操作
79
+ * @param {Function} operation - 要执行的操作
80
+ * @param {Object} [context] - 错误上下文
81
+ * @returns {Promise<{success: boolean, result?: any, error?: Error, recovered?: boolean}>}
82
+ */
83
+ async execute(operation, context = {}) {
84
+ const operationName = context.operation || 'anonymous';
85
+
86
+ try {
87
+ const result = await (typeof operation === 'function' ? operation() : operation);
88
+ return { success: true, result };
89
+ } catch (error) {
90
+ this._errorCount++;
91
+ this._lastError = error;
92
+
93
+ // 记录到历史
94
+ this._addToHistory(error, context);
95
+
96
+ // 记录错误日志
97
+ if (this._logErrors) {
98
+ this._logError(error, context);
99
+ }
100
+
101
+ // 触发错误事件
102
+ this.emit('error', {
103
+ error,
104
+ context,
105
+ boundary: this.name,
106
+ });
107
+
108
+ // 调用全局错误处理器
109
+ let recoveryAction = RecoveryAction.ABORT;
110
+ if (this._onError) {
111
+ try {
112
+ // 使用 Promise.resolve 以支持 async 函数
113
+ recoveryAction = await Promise.resolve(this._onError(error, context, this));
114
+ } catch (handlerError) {
115
+ this._logger.warn(`Error in onError handler: ${handlerError.message}`);
116
+ }
117
+ }
118
+
119
+ // 根据恢复动作处理
120
+ const { action, fallbackResult } = await this._handleRecovery(error, context, recoveryAction);
121
+
122
+ // 触发恢复事件
123
+ if (action !== RecoveryAction.ABORT) {
124
+ this.emit('recovery', {
125
+ error,
126
+ context,
127
+ action,
128
+ boundary: this.name,
129
+ });
130
+
131
+ if (this._onRecovery) {
132
+ try {
133
+ await this._onRecovery(error, context, action, this);
134
+ } catch (recoveryError) {
135
+ log.warn(`Error in onRecovery handler: ${recoveryError.message}`);
136
+ }
137
+ }
138
+ }
139
+
140
+ // 如果需要传播错误,或者没有降级方案且要求传播,则抛出错误
141
+ if (this._propagateErrors || (action === RecoveryAction.ABORT && !fallbackResult)) {
142
+ throw error;
143
+ }
144
+
145
+ return {
146
+ success: false,
147
+ error,
148
+ recovered: action !== RecoveryAction.ABORT,
149
+ fallbackResult,
150
+ action,
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 执行带重试的操作
157
+ * @param {Function} operation - 要执行的操作
158
+ * @param {Object} retryConfig - 重试配置
159
+ * @param {Object} [context] - 错误上下文
160
+ * @returns {Promise<any>}
161
+ */
162
+ async executeWithRetry(operation, retryConfig = {}, context = {}) {
163
+ const { maxAttempts = 3, baseDelay = 1000, shouldRetry = () => true } = retryConfig;
164
+
165
+ let lastError;
166
+
167
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
168
+ try {
169
+ return await this.execute(operation, { ...context, attempt });
170
+ } catch (error) {
171
+ lastError = error;
172
+
173
+ if (attempt >= maxAttempts || !shouldRetry(error, attempt)) {
174
+ throw error;
175
+ }
176
+
177
+ // 计算延迟
178
+ const delay = baseDelay * Math.pow(2, attempt - 1);
179
+ log.debug(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms...`);
180
+
181
+ await new Promise((resolve) => setTimeout(resolve, delay));
182
+ }
183
+ }
184
+
185
+ throw lastError;
186
+ }
187
+
188
+ /**
189
+ * 包装一个函数,使其错误被边界捕获
190
+ * @param {Function} fn - 要包装的函数
191
+ * @param {Object} [defaultContext] - 默认上下文
192
+ * @returns {Function}
193
+ */
194
+ wrap(fn, defaultContext = {}) {
195
+ return async (...args) => {
196
+ const result = await this.execute(() => fn(...args), defaultContext);
197
+ return result.success ? result.result : result.fallbackResult;
198
+ };
199
+ }
200
+
201
+ /**
202
+ * 处理错误恢复
203
+ * @private
204
+ */
205
+ async _handleRecovery(error, context, recoveryAction) {
206
+ let fallbackResult = null;
207
+ let action = recoveryAction;
208
+
209
+ switch (recoveryAction) {
210
+ case RecoveryAction.FALLBACK:
211
+ if (this._fallback) {
212
+ try {
213
+ fallbackResult = await this._fallback(error, context, this);
214
+ action = RecoveryAction.RETRY; // fallback 成功后视为恢复
215
+ } catch (fallbackError) {
216
+ log.warn(`Fallback also failed: ${fallbackError.message}`);
217
+ action = RecoveryAction.ABORT;
218
+ }
219
+ }
220
+ break;
221
+
222
+ case RecoveryAction.SKIP:
223
+ log.debug(`Skipping operation: ${context.operation}`);
224
+ fallbackResult = null;
225
+ break;
226
+
227
+ case RecoveryAction.IGNORE:
228
+ log.debug(`Ignoring error: ${error.message}`);
229
+ fallbackResult = undefined;
230
+ break;
231
+
232
+ default:
233
+ action = RecoveryAction.ABORT;
234
+ }
235
+
236
+ return { action, fallbackResult };
237
+ }
238
+
239
+ /**
240
+ * 记录错误
241
+ * @private
242
+ */
243
+ _logError(error, context) {
244
+ if (!this._logErrors) return;
245
+
246
+ const errorInfo = safeErrorInfo(error);
247
+ const severity = this._determineSeverity(error);
248
+
249
+ const logMessage = `[${this.name}] Error in ${context.operation || 'unknown'}: ${error.message}`;
250
+
251
+ switch (severity) {
252
+ case Severity.LOW:
253
+ this._logger.debug(logMessage);
254
+ break;
255
+ case Severity.MEDIUM:
256
+ this._logger.warn(logMessage);
257
+ break;
258
+ case Severity.HIGH:
259
+ this._logger.error(logMessage);
260
+ break;
261
+ case Severity.CRITICAL:
262
+ this._logger.error(`CRITICAL: ${logMessage}`, { stack: error.stack });
263
+ break;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * 确定错误严重级别
269
+ * @private
270
+ */
271
+ _determineSeverity(error) {
272
+ if (isErrorOfType(error, require('./error').FolikoError)) {
273
+ return error.context?.severity || Severity.MEDIUM;
274
+ }
275
+
276
+ // 根据错误类型和消息判断
277
+ const message = (error.message || '').toLowerCase();
278
+
279
+ if (message.includes('timeout') || message.includes('network')) {
280
+ return Severity.MEDIUM;
281
+ }
282
+
283
+ if (message.includes('memory') || message.includes('fatal')) {
284
+ return Severity.CRITICAL;
285
+ }
286
+
287
+ return Severity.HIGH;
288
+ }
289
+
290
+ /**
291
+ * 添加错误到历史记录
292
+ * @private
293
+ */
294
+ _addToHistory(error, context) {
295
+ this._errorHistory.push({
296
+ timestamp: new Date().toISOString(),
297
+ error: safeErrorInfo(error),
298
+ context,
299
+ });
300
+
301
+ // 保持历史记录大小
302
+ if (this._errorHistory.length > this._maxHistorySize) {
303
+ this._errorHistory.shift();
304
+ }
305
+ }
306
+
307
+ /**
308
+ * 获取错误统计
309
+ * @returns {Object}
310
+ */
311
+ getStats() {
312
+ return {
313
+ totalErrors: this._errorCount,
314
+ lastError: this._lastError ? safeErrorInfo(this._lastError) : null,
315
+ historySize: this._errorHistory.length,
316
+ recentErrors: this._errorHistory.slice(-10),
317
+ };
318
+ }
319
+
320
+ /**
321
+ * 清空错误历史
322
+ */
323
+ clearHistory() {
324
+ this._errorHistory = [];
325
+ this._lastError = null;
326
+ }
327
+ }
328
+
329
+ /**
330
+ * 创建错误边界实例的工厂函数
331
+ * @param {ErrorBoundaryConfig} config
332
+ * @returns {ErrorBoundary}
333
+ */
334
+ function createErrorBoundary(config = {}) {
335
+ return new ErrorBoundary(config);
336
+ }
337
+
338
+ /**
339
+ * 组合多个错误边界
340
+ * @param {ErrorBoundary[]} boundaries
341
+ * @returns {ErrorBoundary}
342
+ */
343
+ function combineBoundaries(boundaries) {
344
+ const combined = new ErrorBoundary({
345
+ name: 'CombinedBoundary',
346
+ });
347
+
348
+ // 将错误传播到所有子边界
349
+ for (const boundary of boundaries) {
350
+ boundary.on('error', (data) => combined.emit('error', data));
351
+ boundary.on('recovery', (data) => combined.emit('recovery', data));
352
+ }
353
+
354
+ return combined;
355
+ }
356
+
357
+ module.exports = {
358
+ ErrorBoundary,
359
+ Severity,
360
+ RecoveryAction,
361
+ createErrorBoundary,
362
+ combineBoundaries,
363
+ };