foliko 1.0.74 → 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 -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 +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 +456 -106
  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 -249
  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/src/index.js CHANGED
@@ -3,23 +3,11 @@
3
3
  * 简约的插件化 Agent 框架
4
4
  */
5
5
 
6
- const {
7
- Framework,
8
- Agent,
9
- Plugin,
10
- PluginManager,
11
- ToolRegistry,
12
- EventEmitter
13
- } = require('./core')
6
+ const { Framework, Agent, Plugin, PluginManager, ToolRegistry, EventEmitter } = require('./core');
14
7
 
15
- const {
16
- SkillManagerPlugin,
17
- WorkflowPlugin
18
- } = require('./capabilities')
8
+ const { SkillManagerPlugin, WorkflowPlugin } = require('./capabilities');
19
9
 
20
- const {
21
- MCPExecutorPlugin
22
- } = require('./executors/mcp-executor')
10
+ const { MCPExecutorPlugin } = require('./executors/mcp-executor');
23
11
 
24
12
  module.exports = {
25
13
  // 核心
@@ -33,5 +21,5 @@ module.exports = {
33
21
  // 能力插件
34
22
  SkillManagerPlugin,
35
23
  WorkflowPlugin,
36
- MCPExecutorPlugin
37
- }
24
+ MCPExecutorPlugin,
25
+ };
@@ -0,0 +1,301 @@
1
+ /**
2
+ * CircuitBreaker 熔断器
3
+ * 用于防止工具调用失败时持续重试导致资源耗尽
4
+ */
5
+
6
+ const { logger } = require('./logger');
7
+
8
+ /**
9
+ * 熔断器状态
10
+ */
11
+ const CircuitState = {
12
+ CLOSED: 'closed', // 正常状态,允许请求通过
13
+ OPEN: 'open', // 熔断状态,拒绝请求
14
+ HALF_OPEN: 'half_open', // 半开状态,允许一个测试请求
15
+ };
16
+
17
+ /**
18
+ * 熔断器选项
19
+ * @typedef {Object} CircuitBreakerOptions
20
+ * @property {number} [failureThreshold=3] - 连续失败次数达到此值时触发熔断
21
+ * @property {number} [successThreshold=2] - 半开状态下连续成功次数达到此值时关闭熔断
22
+ * @property {number} [timeout=60000] - 熔断持续时间(毫秒),超时后进入半开状态
23
+ * @property {string} [name] - 熔断器名称(用于日志)
24
+ */
25
+
26
+ /**
27
+ * CircuitBreaker 熔断器类
28
+ * 使用滑动窗口记录请求成功/失败,帮助防止持续失败的工具调用
29
+ */
30
+ class CircuitBreaker {
31
+ /**
32
+ * @param {CircuitBreakerOptions} options
33
+ */
34
+ constructor(options = {}) {
35
+ this.options = {
36
+ failureThreshold: options.failureThreshold || 3,
37
+ successThreshold: options.successThreshold || 2,
38
+ timeout: options.timeout || 60000, // 默认 1 分钟
39
+ name: options.name || 'default',
40
+ };
41
+
42
+ this._state = CircuitState.CLOSED;
43
+ this._failureCount = 0;
44
+ this._successCount = 0;
45
+ this._lastFailureTime = null;
46
+ this._halfOpenAttempts = 0;
47
+
48
+ this._log = logger.child('CircuitBreaker', { name: this.options.name });
49
+ }
50
+
51
+ /**
52
+ * 获取当前状态
53
+ * @returns {string}
54
+ */
55
+ getState() {
56
+ // 检查是否需要从 OPEN 转为 HALF_OPEN
57
+ if (this._state === CircuitState.OPEN && this._lastFailureTime) {
58
+ const elapsed = Date.now() - this._lastFailureTime;
59
+ if (elapsed >= this.options.timeout) {
60
+ this._transitionTo(CircuitState.HALF_OPEN);
61
+ }
62
+ }
63
+ return this._state;
64
+ }
65
+
66
+ /**
67
+ * 检查是否允许执行
68
+ * @returns {boolean}
69
+ */
70
+ canExecute() {
71
+ const state = this.getState();
72
+ return state === CircuitState.CLOSED || state === CircuitState.HALF_OPEN;
73
+ }
74
+
75
+ /**
76
+ * 执行带熔断保护的函数
77
+ * @param {Function} fn - 要执行的异步函数
78
+ * @returns {Promise<any>}
79
+ * @throws {Error} 如果熔断器处于 OPEN 状态,抛出错误
80
+ */
81
+ async execute(fn) {
82
+ if (!this.canExecute()) {
83
+ throw new Error(
84
+ `Circuit breaker is OPEN for '${this.options.name}'. ` +
85
+ `Tool calls are temporarily blocked. Try again later.`
86
+ );
87
+ }
88
+
89
+ try {
90
+ const result = await fn();
91
+ this._onSuccess();
92
+ return result;
93
+ } catch (err) {
94
+ this._onFailure(err);
95
+ throw err;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 记录成功
101
+ * @private
102
+ */
103
+ _onSuccess() {
104
+ if (this._state === CircuitState.HALF_OPEN) {
105
+ this._successCount++;
106
+ this._halfOpenAttempts++;
107
+ this._log.debug(`Half-open success: ${this._successCount}/${this.options.successThreshold}`);
108
+
109
+ if (this._successCount >= this.options.successThreshold) {
110
+ this._transitionTo(CircuitState.CLOSED);
111
+ }
112
+ } else if (this._state === CircuitState.CLOSED) {
113
+ // 成功后重置失败计数
114
+ this._failureCount = 0;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * 记录失败
120
+ * @param {Error} err
121
+ * @private
122
+ */
123
+ _onFailure(err) {
124
+ this._failureCount++;
125
+ this._lastFailureTime = Date.now();
126
+
127
+ if (this._state === CircuitState.HALF_OPEN) {
128
+ // 半开状态下任何失败都立即打开熔断
129
+ this._log.warn(`Half-open failure, reopening circuit: ${err.message}`);
130
+ this._transitionTo(CircuitState.OPEN);
131
+ } else if (this._state === CircuitState.CLOSED) {
132
+ this._log.warn(
133
+ `Tool call failed (${this._failureCount}/${this.options.failureThreshold}): ${err.message}`
134
+ );
135
+
136
+ if (this._failureCount >= this.options.failureThreshold) {
137
+ this._log.error(
138
+ `Circuit breaker triggered for '${this.options.name}' after ${this._failureCount} consecutive failures`
139
+ );
140
+ this._transitionTo(CircuitState.OPEN);
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 状态转换
147
+ * @param {string} newState
148
+ * @private
149
+ */
150
+ _transitionTo(newState) {
151
+ const oldState = this._state;
152
+ this._state = newState;
153
+
154
+ if (newState === CircuitState.CLOSED) {
155
+ this._failureCount = 0;
156
+ this._successCount = 0;
157
+ this._halfOpenAttempts = 0;
158
+ this._log.info(`Circuit breaker CLOSED (reset)`);
159
+ } else if (newState === CircuitState.HALF_OPEN) {
160
+ this._successCount = 0;
161
+ this._halfOpenAttempts = 0;
162
+ this._log.info(`Circuit breaker HALF_OPEN (testing after ${this.options.timeout}ms timeout)`);
163
+ } else if (newState === CircuitState.OPEN) {
164
+ this._successCount = 0;
165
+ this._halfOpenAttempts = 0;
166
+ this._log.warn(`Circuit breaker OPEN (blocking tool calls for ${this.options.timeout}ms)`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 手动重置熔断器
172
+ */
173
+ reset() {
174
+ this._transitionTo(CircuitState.CLOSED);
175
+ }
176
+
177
+ /**
178
+ * 获取熔断器状态信息
179
+ * @returns {Object}
180
+ */
181
+ getStatus() {
182
+ return {
183
+ name: this.options.name,
184
+ state: this.getState(),
185
+ failureCount: this._failureCount,
186
+ successCount: this._successCount,
187
+ lastFailureTime: this._lastFailureTime,
188
+ nextRetryTime:
189
+ this._state === CircuitState.OPEN && this._lastFailureTime
190
+ ? new Date(this._lastFailureTime + this.options.timeout).toISOString()
191
+ : null,
192
+ config: {
193
+ failureThreshold: this.options.failureThreshold,
194
+ successThreshold: this.options.successThreshold,
195
+ timeout: this.options.timeout,
196
+ },
197
+ };
198
+ }
199
+ }
200
+
201
+ /**
202
+ * CircuitBreakerRegistry - 熔断器注册表
203
+ * 管理多个工具的熔断器
204
+ */
205
+ class CircuitBreakerRegistry {
206
+ constructor() {
207
+ this._breakers = new Map();
208
+ this._log = logger.child('CircuitBreakerRegistry');
209
+ }
210
+
211
+ /**
212
+ * 获取或创建熔断器
213
+ * @param {string} name - 熔断器名称(通常为工具名)
214
+ * @param {CircuitBreakerOptions} options - 选项
215
+ * @returns {CircuitBreaker}
216
+ */
217
+ getOrCreate(name, options = {}) {
218
+ if (!this._breakers.has(name)) {
219
+ this._breakers.set(name, new CircuitBreaker({ ...options, name }));
220
+ }
221
+ return this._breakers.get(name);
222
+ }
223
+
224
+ /**
225
+ * 获取熔断器(如果不存在返回 null)
226
+ * @param {string} name
227
+ * @returns {CircuitBreaker|null}
228
+ */
229
+ get(name) {
230
+ return this._breakers.get(name) || null;
231
+ }
232
+
233
+ /**
234
+ * 检查是否允许执行
235
+ * @param {string} name
236
+ * @returns {boolean}
237
+ */
238
+ canExecute(name) {
239
+ const breaker = this._breakers.get(name);
240
+ return breaker ? breaker.canExecute() : true;
241
+ }
242
+
243
+ /**
244
+ * 执行带熔断保护的函数
245
+ * @param {string} name - 熔断器名称
246
+ * @param {Function} fn - 要执行的函数
247
+ * @param {CircuitBreakerOptions} options - 选项(首次创建时使用)
248
+ * @returns {Promise<any>}
249
+ */
250
+ async execute(name, fn, options = {}) {
251
+ const breaker = this.getOrCreate(name, options);
252
+ return breaker.execute(fn);
253
+ }
254
+
255
+ /**
256
+ * 获取所有熔断器状态
257
+ * @returns {Object[]}
258
+ */
259
+ getAllStatus() {
260
+ const result = [];
261
+ for (const [name, breaker] of this._breakers) {
262
+ result.push(breaker.getStatus());
263
+ }
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * 重置所有熔断器
269
+ */
270
+ resetAll() {
271
+ for (const breaker of this._breakers.values()) {
272
+ breaker.reset();
273
+ }
274
+ this._log.info('All circuit breakers reset');
275
+ }
276
+
277
+ /**
278
+ * 移除熔断器
279
+ * @param {string} name
280
+ */
281
+ remove(name) {
282
+ this._breakers.delete(name);
283
+ }
284
+
285
+ /**
286
+ * 清空所有熔断器
287
+ */
288
+ clear() {
289
+ this._breakers.clear();
290
+ }
291
+ }
292
+
293
+ // 导出单例
294
+ const globalRegistry = new CircuitBreakerRegistry();
295
+
296
+ module.exports = {
297
+ CircuitBreaker,
298
+ CircuitBreakerRegistry,
299
+ CircuitState,
300
+ globalCircuitBreaker: globalRegistry,
301
+ };
@@ -0,0 +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
+ };