feihong-code 7.2.0 → 7.6.0

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 (204) hide show
  1. package/.github/workflows/ci.yml +54 -1
  2. package/.github/workflows/release.yml +30 -0
  3. package/.github/workflows/swebench-real.yml +84 -0
  4. package/CHANGELOG.md +41 -39
  5. package/CONTRIBUTING.md +9 -2
  6. package/README.md +56 -6
  7. package/README.self-evolve.md +279 -274
  8. package/dist/agent/change-manager.js +1 -1
  9. package/dist/agent/code-rag.js +1 -1
  10. package/dist/agent/code-review.js +1 -1
  11. package/dist/agent/code-writer.js +1 -1
  12. package/dist/agent/completion-engine.js +76 -45
  13. package/dist/agent/completion-engine.js.map +1 -1
  14. package/dist/agent/completion-postprocess.js +109 -0
  15. package/dist/agent/completion-postprocess.js.map +1 -0
  16. package/dist/agent/context-budget.js +121 -0
  17. package/dist/agent/context-budget.js.map +1 -0
  18. package/dist/agent/context-compactor.js +58 -0
  19. package/dist/agent/context-compactor.js.map +1 -1
  20. package/dist/agent/experience.js +1 -1
  21. package/dist/agent/layered-memory.js +1 -1
  22. package/dist/agent/lint.js +121 -0
  23. package/dist/agent/lint.js.map +1 -0
  24. package/dist/agent/orchestrator.js +106 -31
  25. package/dist/agent/orchestrator.js.map +1 -1
  26. package/dist/agent/parallel-orchestrator.js +1 -1
  27. package/dist/agent/planner.js +8 -3
  28. package/dist/agent/planner.js.map +1 -1
  29. package/dist/agent/prompts.js +55 -55
  30. package/dist/agent/quality-gate.js +1 -1
  31. package/dist/agent/repo-context.js +1 -1
  32. package/dist/agent/repo-reader.js +1 -1
  33. package/dist/agent/repo-underwriter.js +1 -1
  34. package/dist/agent/self-correction.js +25 -25
  35. package/dist/agent/self-heal.js +32 -27
  36. package/dist/agent/self-heal.js.map +1 -1
  37. package/dist/agent/self-improver.js +1 -1
  38. package/dist/agent/subagent-summary.js +1 -1
  39. package/dist/agent/subagent.js +1 -1
  40. package/dist/agent/swe-agent.js +1 -1
  41. package/dist/agent/swe-verifier.js +1 -1
  42. package/dist/agent/symbol-index.js +1 -1
  43. package/dist/agent/team.js +1 -1
  44. package/dist/agent/type-checker.js +1 -1
  45. package/dist/cli/commands.js +16 -0
  46. package/dist/cli/commands.js.map +1 -1
  47. package/dist/cli/index.js +66 -2
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/cli/multi-pane-tui.js +217 -0
  50. package/dist/cli/multi-pane-tui.js.map +1 -0
  51. package/dist/cli/repl.js +1 -1
  52. package/dist/cli/run.js +168 -42
  53. package/dist/cli/run.js.map +1 -1
  54. package/dist/cli/self-evolve-cli.js +168 -0
  55. package/dist/cli/tui-run.js +79 -0
  56. package/dist/cli/tui-run.js.map +1 -0
  57. package/dist/cli/tui.js +1 -1
  58. package/dist/cli/tui.js.map +1 -1
  59. package/dist/cli/version.js +3 -3
  60. package/dist/cli/version.js.map +1 -1
  61. package/dist/enterprise/audit.js +1 -1
  62. package/dist/enterprise/index.js +1 -1
  63. package/dist/enterprise/policy.js +1 -1
  64. package/dist/enterprise/quota.js +1 -1
  65. package/dist/enterprise/tenant.js +1 -1
  66. package/dist/harness/executor.js +1 -1
  67. package/dist/harness/index.js +1 -1
  68. package/dist/harness/loader.js +1 -1
  69. package/dist/harness/types.js +1 -1
  70. package/dist/harness/verifier.js +52 -12
  71. package/dist/harness/verifier.js.map +1 -1
  72. package/dist/integrations/feishu-bridge.js +348 -0
  73. package/dist/integrations/feishu-bridge.js.map +1 -0
  74. package/dist/integrations/github-mcp.js +137 -0
  75. package/dist/integrations/github-mcp.js.map +1 -0
  76. package/dist/integrations/wechat-bridge.js +499 -0
  77. package/dist/integrations/wechat-bridge.js.map +1 -0
  78. package/dist/integrations/yuanbao-bridge.js +326 -0
  79. package/dist/integrations/yuanbao-bridge.js.map +1 -0
  80. package/dist/lsp/lsp-client.js +183 -0
  81. package/dist/lsp/lsp-client.js.map +1 -0
  82. package/dist/lsp/lsp-service.js +159 -0
  83. package/dist/lsp/lsp-service.js.map +1 -0
  84. package/dist/models/model-router.js +1 -1
  85. package/dist/models/model.dto.js +1 -1
  86. package/dist/models/providers/ollama.provider.js +1 -1
  87. package/dist/models/providers/openai-compatible.provider.js +1 -1
  88. package/dist/plugins/manager.js +50 -2
  89. package/dist/plugins/manager.js.map +1 -1
  90. package/dist/plugins/plugin-loader.js +1 -1
  91. package/dist/runtime/event-log.js +1 -1
  92. package/dist/runtime/git.js +1 -1
  93. package/dist/runtime/hooks.js +71 -4
  94. package/dist/runtime/hooks.js.map +1 -1
  95. package/dist/runtime/session-persist.js +1 -1
  96. package/dist/runtime/worktree.js +1 -1
  97. package/dist/self-evolve/manager.js +517 -401
  98. package/dist/self-evolve/self-heal-scheduler.js +160 -0
  99. package/dist/self-evolve/self-heal-scheduler.js.map +1 -0
  100. package/dist/shared/concurrency.js +1 -1
  101. package/dist/shared/config.js +4 -2
  102. package/dist/shared/config.js.map +1 -1
  103. package/dist/shared/errors.js +1 -1
  104. package/dist/shared/i18n.js +109 -101
  105. package/dist/shared/i18n.js.map +1 -1
  106. package/dist/shared/secure-store.js +12 -5
  107. package/dist/shared/secure-store.js.map +1 -1
  108. package/dist/shared/types.js +1 -1
  109. package/dist/skills/goal.js +1 -1
  110. package/dist/skills/grill.js +1 -1
  111. package/dist/skills/plan.js +1 -1
  112. package/dist/skills/pua-hooks.js +273 -0
  113. package/dist/skills/pua-hooks.js.map +1 -0
  114. package/dist/skills/self-heal.js +9 -3
  115. package/dist/skills/self-heal.js.map +1 -1
  116. package/dist/skills/skill-loader.js +1 -1
  117. package/dist/skills/skill-market.js +84 -8
  118. package/dist/skills/skill-market.js.map +1 -1
  119. package/dist/tools/analysis/code-analyzer.js +1 -1
  120. package/dist/tools/file/edit.tool.js +18 -3
  121. package/dist/tools/file/edit.tool.js.map +1 -1
  122. package/dist/tools/file/list.tool.js +1 -1
  123. package/dist/tools/file/read.tool.js +1 -1
  124. package/dist/tools/file/write.tool.js +1 -1
  125. package/dist/tools/generator/code-generator.js +41 -41
  126. package/dist/tools/generator/test-generator.js +10 -10
  127. package/dist/tools/index.js +1 -1
  128. package/dist/tools/mcp/index.js +1 -1
  129. package/dist/tools/mcp/mcp-client.js +1 -1
  130. package/dist/tools/safe-path.js +1 -1
  131. package/dist/tools/search/grep.tool.js +1 -1
  132. package/dist/tools/shell/exec.js +24 -2
  133. package/dist/tools/shell/exec.js.map +1 -1
  134. package/dist/tools/shell/run-shell.tool.js +1 -1
  135. package/dist/tools/skills/load-skill.tool.js +1 -1
  136. package/dist/tools/tool.registry.js +10 -1
  137. package/dist/tools/tool.registry.js.map +1 -1
  138. package/dist/tools/verify/build-check.tool.js +1 -1
  139. package/dist/tools/verify/test-run.tool.js +1 -1
  140. package/dist/tools/web/web.tool.js +1 -1
  141. package/dist/voice/voice-programming.js +32 -6
  142. package/dist/voice/voice-programming.js.map +1 -1
  143. package/dist/web/auth.js +34 -2
  144. package/dist/web/auth.js.map +1 -1
  145. package/dist/web/channels.js +1 -1
  146. package/dist/web/extra-apis.js +440 -0
  147. package/dist/web/extra-apis.js.map +1 -0
  148. package/dist/web/public/css/style.css +99 -72
  149. package/dist/web/public/index.html +44 -1
  150. package/dist/web/public/js/api.js +64 -2
  151. package/dist/web/public/js/app.js +151 -128
  152. package/dist/web/public/js/mock-api.js +25 -0
  153. package/dist/web/public/js/monaco-editor.js +161 -1
  154. package/dist/web/public/js/ui.js +292 -56
  155. package/dist/web/public/service-worker.js +5 -29
  156. package/dist/web/server.js +178 -61
  157. package/dist/web/server.js.map +1 -1
  158. package/dist/web/task-queue.js +6 -21
  159. package/dist/web/task-queue.js.map +1 -1
  160. package/docs/App/344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +1 -1
  161. package/docs/App/346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +1 -1
  162. package/docs/BENCHMARK_REPORT_en.md +62 -5
  163. package/docs/BENCHMARK_REPORT_zh.md +66 -9
  164. package/docs/DPA.md +65 -0
  165. package/docs/DPA/346/225/260/346/215/256/345/244/204/347/220/206/345/215/217/350/256/256/346/250/241/346/235/277_v1.0.md +69 -0
  166. package/docs/Deployment_Guide_EN.md +1 -1
  167. package/docs/SECURITY_WHITEPAPER.md +74 -0
  168. package/docs/SELF-EVOLVE-GUIDE.md +1 -1
  169. package/docs/SWE_BENCH_REPORT.md +73 -0
  170. package/docs/TECHNICAL_SPEC_en.md +1 -1
  171. package/docs/TECHNICAL_SPEC_zh.md +1 -1
  172. package/docs/Technical_Manual_EN.md +1 -1
  173. package/docs/UPDATE_NOTES_en.md +1 -1
  174. package/docs/UPDATE_NOTES_zh.md +1 -1
  175. package/docs/UPGRADE_7.2.0.md +309 -0
  176. package/docs/UPGRADE_GUIDE_7_5.md +86 -0
  177. package/docs/UPGRADE_GUIDE_7_6.md +153 -0
  178. package/docs/User_Manual_EN.md +1 -1
  179. package/docs/VOLTAGENT_SKILLS_CATALOG.md +95 -0
  180. package/docs/error-codes.md +1 -1
  181. package/docs/geo-feihong-code.html +391 -0
  182. package/docs//344/272/247/345/223/201/345/274/200/345/217/221/346/226/207/346/241/243.md +5 -3
  183. package/docs//344/275/277/347/224/250/350/257/264/346/230/216/344/271/246.md +140 -231
  184. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_50/350/275/256/346/267/261/345/272/246/345/256/241/350/256/241_v7.2.0.md +208 -0
  185. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_6/351/241/271/351/207/215/347/202/271/346/240/270/346/237/245_v7.2.0.md +152 -0
  186. package/docs//345/205/250/351/235/242/345/244/215/347/233/230_/350/277/220/350/241/214/346/227/266/345/256/236/346/265/213/347/211/210_v7.2.0.md +271 -0
  187. package/docs//345/220/210/350/247/204/350/256/244/350/257/201/345/207/206/345/244/207/346/270/205/345/215/225_v7.2.0.md +59 -0
  188. package/docs//345/256/211/345/205/250/347/231/275/347/232/256/344/271/246_v7.2.0.md +84 -0
  189. package/docs//346/212/200/346/234/257/350/257/264/346/230/216/344/271/246.md +240 -149
  190. package/docs//351/203/250/347/275/262/350/257/264/346/230/216/344/271/246.md +2 -2
  191. package/electron/main.js +4 -202
  192. package/package.json +182 -172
  193. package/templates/skills/api-design/SKILL.md +27 -0
  194. package/templates/skills/code-review/SKILL.md +35 -0
  195. package/templates/skills/dependency-upgrade/SKILL.md +29 -0
  196. package/templates/skills/doc-gen/SKILL.md +29 -0
  197. package/templates/skills/git-flow/SKILL.md +26 -0
  198. package/templates/skills/onboarding/SKILL.md +27 -0
  199. package/templates/skills/performance/SKILL.md +29 -0
  200. package/templates/skills/refactor/SKILL.md +28 -0
  201. package/templates/skills/security-audit/SKILL.md +28 -0
  202. package/templates/skills/test-gen/SKILL.md +30 -0
  203. package/docs/DEPLOYMENT.md +0 -382
  204. package/docs/MODEL_FINETUNING.md +0 -405
@@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.startWebServer = startWebServer;
40
40
  /**
41
- * 飞虹 Code (Muse Code 参照复刻)
41
+ * 飞虹 Code (对标 Muse Code · 自研内核)
42
42
  * 晋江市飞虹智科技企业管理有限公司 · 飞扬企源研发中心 · 负责人:吴赐虹
43
43
  *
44
44
  * M5 Web 控制台:自包含 Express 服务骨架 + 多视图 API。
@@ -56,7 +56,9 @@ const crypto_1 = require("crypto");
56
56
  const path_1 = require("path");
57
57
  const fs_1 = require("fs");
58
58
  const child_process_1 = require("child_process");
59
+ const node_dns_1 = __importDefault(require("node:dns"));
59
60
  const auth_1 = require("./auth");
61
+ const extra_apis_1 = require("./extra-apis");
60
62
  const task_queue_1 = require("./task-queue");
61
63
  const version_1 = require("../cli/version");
62
64
  const i18n_1 = require("../shared/i18n");
@@ -65,6 +67,7 @@ const config_1 = require("../shared/config");
65
67
  const model_router_1 = require("../models/model-router");
66
68
  const openai_compatible_provider_1 = require("../models/providers/openai-compatible.provider");
67
69
  const completion_engine_1 = require("../agent/completion-engine");
70
+ const lint_1 = require("../agent/lint");
68
71
  const symbol_index_1 = require("../agent/symbol-index");
69
72
  const change_manager_1 = require("../agent/change-manager");
70
73
  const design_to_code_1 = require("../agent/design-to-code");
@@ -78,6 +81,10 @@ const mcp_client_1 = require("../tools/mcp/mcp-client");
78
81
  const memory_1 = require("../memory");
79
82
  const auto_summarize_1 = require("../memory/auto-summarize");
80
83
  const secure_store_1 = require("../shared/secure-store");
84
+ const wechat_bridge_1 = require("../integrations/wechat-bridge");
85
+ const feishu_bridge_1 = require("../integrations/feishu-bridge");
86
+ const yuanbao_bridge_1 = require("../integrations/yuanbao-bridge");
87
+ const collaboration_1 = require("../integrations/collaboration");
81
88
  /**
82
89
  * 启动 Web 控制台。
83
90
  * - 端口:opts.port > FH_WEB_PORT > 8080
@@ -97,6 +104,10 @@ function startWebServer(opts = {}) {
97
104
  const app = (0, express_1.default)();
98
105
  // 放宽请求体上限以支持截图/图片 base64 上传(8MB),仍可有效防御内存耗尽
99
106
  app.use(express_1.default.json({ limit: '8mb' }));
107
+ // 微信回调使用 XML body,单独路由用 text 解析
108
+ app.use('/api/wechat/callback', express_1.default.text({ type: ['*/xml', 'text/xml', 'application/xml'], limit: '1mb' }));
109
+ // 元宝回调需要原始 body 用于 HMAC 签名校验
110
+ app.use('/api/yuanbao/callback', express_1.default.raw({ type: 'application/json', limit: '1mb' }));
100
111
  // 静态仪表盘(开发时直接从 src 读取,避免 dist 被锁)
101
112
  const publicDir = process.env.FH_WEB_SRC_PUBLIC || (0, path_1.join)(__dirname, 'public');
102
113
  app.use(express_1.default.static(publicDir, { maxAge: '0' }));
@@ -135,6 +146,18 @@ function startWebServer(opts = {}) {
135
146
  catch (e) { /* 暂存失败不影响任务执行 */ }
136
147
  },
137
148
  });
149
+ // 微信桥接:注入 TaskQueue 引用并初始化
150
+ (0, wechat_bridge_1.setWechatTaskQueue)(queue);
151
+ const wechatConfig = (0, wechat_bridge_1.initWechatBridge)();
152
+ // 飞书桥接:注入 TaskQueue 和 FeishuIntegration,初始化
153
+ const feishuConfig = (0, feishu_bridge_1.initFeishuBridge)();
154
+ if ((0, feishu_bridge_1.isFeishuEnabled)(feishuConfig)) {
155
+ const feishuIntegration = new collaboration_1.FeishuIntegration({ appId: feishuConfig.appId, appSecret: feishuConfig.appSecret });
156
+ (0, feishu_bridge_1.setFeishuBridgeDeps)(queue, feishuIntegration);
157
+ }
158
+ // 元宝/豆包桥接:注入 TaskQueue 引用并初始化
159
+ (0, yuanbao_bridge_1.setYuanbaoTaskQueue)(queue);
160
+ const yuanbaoConfig = (0, yuanbao_bridge_1.initYuanbaoBridge)();
138
161
  // 公开健康检查(仅暴露版本/状态等观测信息,无敏感数据)
139
162
  app.get('/api/health', (_req, res) => {
140
163
  res.json({
@@ -144,9 +167,43 @@ function startWebServer(opts = {}) {
144
167
  signature: version_1.SIGNATURE,
145
168
  enterprise: (0, enterprise_1.isEnterpriseEnabled)(),
146
169
  lang: (0, i18n_1.getLang)(),
170
+ wechat: (0, wechat_bridge_1.isWechatEnabled)(wechatConfig) ? wechatConfig.mode : 'disabled',
171
+ feishu: (0, feishu_bridge_1.isFeishuEnabled)(feishuConfig) ? 'enabled' : 'disabled',
172
+ yuanbao: (0, yuanbao_bridge_1.isYuanbaoEnabled)(yuanbaoConfig) ? 'enabled' : 'disabled',
147
173
  time: new Date().toISOString(),
148
174
  });
149
175
  });
176
+ // 微信桥接回调(GET=URL验证,POST=消息接收),无需 Bearer Token(微信服务器调用)
177
+ app.get('/api/wechat/callback', async (req, res) => {
178
+ const result = (await (0, wechat_bridge_1.handleWechatCallback)(wechatConfig, req.query, ''));
179
+ res.status(result.status);
180
+ res.setHeader('Content-Type', result.contentType);
181
+ res.send(result.body);
182
+ });
183
+ app.post('/api/wechat/callback', async (req, res) => {
184
+ const body = typeof req.body === 'string' ? req.body : JSON.stringify(req.body || '');
185
+ const result = (await (0, wechat_bridge_1.handleWechatCallback)(wechatConfig, req.query, body));
186
+ res.status(result.status);
187
+ res.setHeader('Content-Type', result.contentType);
188
+ res.send(result.body);
189
+ });
190
+ // 飞书桥接回调(事件订阅,无需 Bearer Token)
191
+ app.post('/api/feishu/callback', async (req, res) => {
192
+ const body = (req.body || {});
193
+ const result = (await (0, feishu_bridge_1.handleFeishuCallback)(feishuConfig, body));
194
+ res.status(result.status);
195
+ res.setHeader('Content-Type', result.contentType);
196
+ res.send(result.body);
197
+ });
198
+ // 元宝/豆包桥接回调(webhook,无需 Bearer Token,原始 body 用于签名校验)
199
+ app.post('/api/yuanbao/callback', async (req, res) => {
200
+ const rawBody = Buffer.isBuffer(req.body) ? req.body.toString('utf8') : (typeof req.body === 'string' ? req.body : JSON.stringify(req.body || ''));
201
+ const headers = req.headers;
202
+ const result = (await (0, yuanbao_bridge_1.handleYuanbaoCallback)(yuanbaoConfig, rawBody, headers));
203
+ res.status(result.status);
204
+ res.setHeader('Content-Type', result.contentType);
205
+ res.send(result.body);
206
+ });
150
207
  // 手机号直登:无短信验证,生成本地会话令牌
151
208
  app.post('/api/auth/login', (req, res) => {
152
209
  const body = (req.body ?? {});
@@ -158,23 +215,19 @@ function startWebServer(opts = {}) {
158
215
  // 使用新的登录方法,支持首次登录检测
159
216
  const result = sessions.login(phone);
160
217
  const session = sessions.get(result.token);
161
- // 如果是首次登录,自动创建引导任务
218
+ // 首次登录:返回引导任务定义(不自动提交执行,由用户在前端点「运行」时再真正发起)
162
219
  let welcomeTasks = [];
163
220
  if (result.isFirstLogin) {
164
221
  // 标记会话为首次登录(用于后续接口识别)
165
222
  if (session) {
166
223
  session.isFirstLogin = true;
167
224
  }
168
- // 创建引导任务
225
+ // 只返回任务定义,taskId 留空,前端点运行时才提交到任务队列
169
226
  for (const task of auth_1.WELCOME_TASKS) {
170
- const record = queue.submit(task.goal, {
171
- workspaceDir: serverWorkspaceDir,
172
- agentType: 'general',
173
- });
174
227
  welcomeTasks.push({
175
228
  ...task,
176
- taskId: record.id,
177
- status: record.status,
229
+ taskId: '',
230
+ status: 'pending',
178
231
  });
179
232
  }
180
233
  }
@@ -270,7 +323,21 @@ function startWebServer(opts = {}) {
270
323
  const history = (0, auto_summarize_1.getSummaryHistory)(memoryConfig, limit);
271
324
  res.json({ ok: true, history });
272
325
  });
273
- app.use('/api', (0, auth_1.requireToken)(token, sessions));
326
+ app.post('/api/auth/clear-first-login', (req, res) => {
327
+ const header = req.header('authorization') || '';
328
+ const m = /^Bearer\s+(.+)$/i.exec(header);
329
+ if (!m) {
330
+ res.status(401).json({ ok: false, error: 'unauthorized' });
331
+ return;
332
+ }
333
+ const token = m[1];
334
+ const ok = sessions.clearFirstLogin(token);
335
+ res.json({ ok, cleared: ok });
336
+ });
337
+ app.use('/api', (0, auth_1.requireToken)(token, sessions, ['/api/security/public-key']));
338
+ // ===== v7.2.0 能力接线层:voice / knowledge / figma / sso / collaboration / plugins / self-correction =====
339
+ // 将原"孤立代码"模块接入 Web API(修复:从 404 变为可调用)
340
+ (0, extra_apis_1.registerExtraApis)(app, { dataDir: (0, path_1.join)((0, config_1.resolveHomeDir)(), '.feihong-code') });
274
341
  app.get('/api/auth/me', (req, res) => {
275
342
  const session = req.user;
276
343
  res.json({
@@ -776,6 +843,7 @@ function startWebServer(opts = {}) {
776
843
  const cursorOffset = typeof body?.cursorOffset === 'number' ? body.cursorOffset : fileContent.length;
777
844
  const mode = body?.mode === 'full' ? 'full' : 'quick';
778
845
  const language = typeof body?.language === 'string' ? body.language : undefined;
846
+ const crossFileContext = typeof body?.crossFileContext === 'string' ? body.crossFileContext : undefined;
779
847
  if (!filePath || !fileContent) {
780
848
  res.status(400).json({ ok: false, error: '缺少 filePath 或 fileContent' });
781
849
  return;
@@ -791,6 +859,7 @@ function startWebServer(opts = {}) {
791
859
  cursorOffset,
792
860
  mode,
793
861
  language,
862
+ crossFileContext,
794
863
  });
795
864
  res.json({ ok: true, ...result });
796
865
  }
@@ -798,6 +867,17 @@ function startWebServer(opts = {}) {
798
867
  res.status(500).json({ ok: false, error: '补全失败: ' + e.message, suggestions: [] });
799
868
  }
800
869
  });
870
+ app.post('/api/lint', (req, res) => {
871
+ const body = (req.body ?? {});
872
+ const code = typeof body?.code === 'string' ? body.code : '';
873
+ const language = typeof body?.language === 'string' ? body.language : undefined;
874
+ if (!code) {
875
+ res.status(400).json({ ok: false, error: '缺少 code' });
876
+ return;
877
+ }
878
+ const errors = (0, lint_1.lintSnippet)(code, language);
879
+ res.json({ ok: true, errors, clean: errors.length === 0 });
880
+ });
801
881
  /* ========== 阶段一-2:补全 Pro API ========== */
802
882
  // 记录用户接受的补全(用于连续推荐)
803
883
  app.post('/api/completion/accept', (req, res) => {
@@ -844,6 +924,15 @@ function startWebServer(opts = {}) {
844
924
  }
845
925
  });
846
926
  /* ========== P2-1: 设计稿转代码 API(多模态,图片→代码) ========== */
927
+ // P1-3: 设计稿转代码引擎状态查询
928
+ app.get('/api/design-to-code', (_req, res) => {
929
+ res.json({
930
+ ok: true,
931
+ configured: !!designToCodeEngine,
932
+ framework: designToCodeEngine ? 'html' : null,
933
+ note: designToCodeEngine ? '已配置多模态模型' : '未配置多模态模型(POST /api/design-to-code 需 vision 模型)',
934
+ });
935
+ });
847
936
  app.post('/api/design-to-code', async (req, res) => {
848
937
  const body = (req.body ?? {});
849
938
  const image = typeof body?.image === 'string' ? body.image.trim() : '';
@@ -1337,6 +1426,15 @@ function startWebServer(opts = {}) {
1337
1426
  .catch((e) => res.status(500).json({ ok: false, error: e.message }));
1338
1427
  });
1339
1428
  // Cron 任务 CRUD
1429
+ app.get('/api/event-driven/cron', (_req, res) => {
1430
+ try {
1431
+ const cfg = eventDrivenManager.getConfig();
1432
+ res.json({ ok: true, cronTasks: cfg.cronTasks ?? [] });
1433
+ }
1434
+ catch (e) {
1435
+ res.status(500).json({ ok: false, error: e.message });
1436
+ }
1437
+ });
1340
1438
  app.post('/api/event-driven/cron', (req, res) => {
1341
1439
  const body = (req.body ?? {});
1342
1440
  if (!body.name || !body.cron || !body.task) {
@@ -1609,18 +1707,18 @@ function startWebServer(opts = {}) {
1609
1707
  res.status(400).json({ ok: false, error: '仅支持 Windows 系统' });
1610
1708
  return;
1611
1709
  }
1612
- const script = `
1613
- Add-Type -AssemblyName System.Windows.Forms
1614
- Add-Type -AssemblyName System.Drawing
1615
- $screen = [System.Windows.Forms.Screen]::PrimaryScreen
1616
- $bounds = $screen.Bounds
1617
- $bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
1618
- $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
1619
- $graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
1620
- $ms = New-Object System.IO.MemoryStream
1621
- $bitmap.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
1622
- $bytes = $ms.ToArray()
1623
- [Convert]::ToBase64String($bytes)
1710
+ const script = `
1711
+ Add-Type -AssemblyName System.Windows.Forms
1712
+ Add-Type -AssemblyName System.Drawing
1713
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen
1714
+ $bounds = $screen.Bounds
1715
+ $bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
1716
+ $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
1717
+ $graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
1718
+ $ms = New-Object System.IO.MemoryStream
1719
+ $bitmap.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
1720
+ $bytes = $ms.ToArray()
1721
+ [Convert]::ToBase64String($bytes)
1624
1722
  `;
1625
1723
  const base64 = await runPowerShell(script);
1626
1724
  res.json({ ok: true, image: 'data:image/png;base64,' + base64, width: 1920, height: 1080 });
@@ -1639,17 +1737,17 @@ function startWebServer(opts = {}) {
1639
1737
  res.status(400).json({ ok: false, error: '缺少 x 或 y 坐标' });
1640
1738
  return;
1641
1739
  }
1642
- const script = `
1643
- Add-Type @"
1644
- using System;
1645
- using System.Runtime.InteropServices;
1646
- public class MouseHelper {
1647
- [DllImport("user32.dll")]
1648
- public static extern bool SetCursorPos(int X, int Y);
1649
- }
1650
- "@
1651
- [MouseHelper]::SetCursorPos(${x}, ${y}) | Out-Null
1652
- Write-Output "ok"
1740
+ const script = `
1741
+ Add-Type @"
1742
+ using System;
1743
+ using System.Runtime.InteropServices;
1744
+ public class MouseHelper {
1745
+ [DllImport("user32.dll")]
1746
+ public static extern bool SetCursorPos(int X, int Y);
1747
+ }
1748
+ "@
1749
+ [MouseHelper]::SetCursorPos(${x}, ${y}) | Out-Null
1750
+ Write-Output "ok"
1653
1751
  `;
1654
1752
  await runPowerShell(script);
1655
1753
  res.json({ ok: true, x, y });
@@ -1676,20 +1774,20 @@ function startWebServer(opts = {}) {
1676
1774
  const clickPart = doubleClick
1677
1775
  ? `[MouseHelper]::mouse_event(${clickFlag}, 0, 0, 0, 0); [MouseHelper]::mouse_event(${upFlag}, 0, 0, 0, 0); Start-Sleep -Milliseconds 100; [MouseHelper]::mouse_event(${clickFlag}, 0, 0, 0, 0); [MouseHelper]::mouse_event(${upFlag}, 0, 0, 0, 0);`
1678
1776
  : `[MouseHelper]::mouse_event(${clickFlag}, 0, 0, 0, 0); [MouseHelper]::mouse_event(${upFlag}, 0, 0, 0, 0);`;
1679
- const script = `
1680
- Add-Type @"
1681
- using System;
1682
- using System.Runtime.InteropServices;
1683
- public class MouseHelper {
1684
- [DllImport("user32.dll")]
1685
- public static extern bool SetCursorPos(int X, int Y);
1686
- [DllImport("user32.dll")]
1687
- public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
1688
- }
1689
- "@
1690
- ${movePart}
1691
- ${clickPart}
1692
- Write-Output "ok"
1777
+ const script = `
1778
+ Add-Type @"
1779
+ using System;
1780
+ using System.Runtime.InteropServices;
1781
+ public class MouseHelper {
1782
+ [DllImport("user32.dll")]
1783
+ public static extern bool SetCursorPos(int X, int Y);
1784
+ [DllImport("user32.dll")]
1785
+ public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
1786
+ }
1787
+ "@
1788
+ ${movePart}
1789
+ ${clickPart}
1790
+ Write-Output "ok"
1693
1791
  `;
1694
1792
  await runPowerShell(script);
1695
1793
  res.json({ ok: true, button, x, y, double: doubleClick });
@@ -1709,10 +1807,10 @@ function startWebServer(opts = {}) {
1709
1807
  }
1710
1808
  // SendKeys 需要转义特殊字符
1711
1809
  const escaped = text.replace(/([+^%~(){}])/g, '{$1}');
1712
- const script = `
1713
- Add-Type -AssemblyName System.Windows.Forms
1714
- [System.Windows.Forms.SendKeys]::SendWait('${escaped.replace(/'/g, "''")}')
1715
- Write-Output "ok"
1810
+ const script = `
1811
+ Add-Type -AssemblyName System.Windows.Forms
1812
+ [System.Windows.Forms.SendKeys]::SendWait('${escaped.replace(/'/g, "''")}')
1813
+ Write-Output "ok"
1716
1814
  `;
1717
1815
  await runPowerShell(script);
1718
1816
  res.json({ ok: true, text });
@@ -1730,10 +1828,10 @@ function startWebServer(opts = {}) {
1730
1828
  res.status(400).json({ ok: false, error: '缺少 key 字段' });
1731
1829
  return;
1732
1830
  }
1733
- const script = `
1734
- Add-Type -AssemblyName System.Windows.Forms
1735
- [System.Windows.Forms.SendKeys]::SendWait('${key.replace(/'/g, "''")}')
1736
- Write-Output "ok"
1831
+ const script = `
1832
+ Add-Type -AssemblyName System.Windows.Forms
1833
+ [System.Windows.Forms.SendKeys]::SendWait('${key.replace(/'/g, "''")}')
1834
+ Write-Output "ok"
1737
1835
  `;
1738
1836
  await runPowerShell(script);
1739
1837
  res.json({ ok: true, key });
@@ -1791,7 +1889,7 @@ function startWebServer(opts = {}) {
1791
1889
  const headers = { 'User-Agent': 'fhcode-node/1.0' };
1792
1890
  if (node.apiKey)
1793
1891
  headers['Authorization'] = 'Bearer ' + node.apiKey;
1794
- const r = await fetchWithRetry(url, 10000, 0);
1892
+ const r = await fetchWithRetry(url, 25000, 0);
1795
1893
  if (!r.ok)
1796
1894
  return { ok: false, error: `HTTP ${r.status}` };
1797
1895
  if (node.type === 'http') {
@@ -2068,7 +2166,10 @@ function startWebServer(opts = {}) {
2068
2166
  /* ========== 技能市场(插件市场):聚合 ClawHub + Agent-Foundry + 自定义节点 + 自定义来源 ========== */
2069
2167
  const CLAWHUB_API = 'https://clawhub.ai/api/v1/skills';
2070
2168
  const AGENT_FOUNDRY_CATALOG = 'https://raw.githubusercontent.com/hebertzhu/agent-foundry/main/catalog/skills-catalog.json';
2071
- async function fetchWithRetry(url, timeoutMs = 15000, retries = 1) {
2169
+ async function fetchWithRetry(url, timeoutMs = 25000, retries = 1) {
2170
+ // 强制 IPv4 优先:规避 IPv6 链路故障(部分网络 IPv6 路由不可达时,
2171
+ // 默认 verbatim 解析会优先走 IPv6 导致连接超时/失败)。幂等,重复调用无副作用。
2172
+ node_dns_1.default.setDefaultResultOrder('ipv4first');
2072
2173
  let lastErr;
2073
2174
  for (let attempt = 0; attempt <= retries; attempt++) {
2074
2175
  const ctrl = new AbortController();
@@ -2118,7 +2219,7 @@ function startWebServer(opts = {}) {
2118
2219
  }
2119
2220
  async function fetchAgentFoundrySkills(keyword, limit) {
2120
2221
  try {
2121
- const r = await fetchWithRetry(AGENT_FOUNDRY_CATALOG);
2222
+ const r = await fetchWithRetry(AGENT_FOUNDRY_CATALOG, 30000);
2122
2223
  if (!r.ok)
2123
2224
  return [];
2124
2225
  const data = (await r.json());
@@ -2189,7 +2290,7 @@ function startWebServer(opts = {}) {
2189
2290
  if (!src.enabled || src.type !== 'skills')
2190
2291
  continue;
2191
2292
  try {
2192
- const r = await fetchWithRetry(src.url, 10000, 0);
2293
+ const r = await fetchWithRetry(src.url, 25000, 0);
2193
2294
  if (r.ok) {
2194
2295
  const data = await r.json();
2195
2296
  const list = Array.isArray(data) ? data : (data.items || data.skills || []);
@@ -2495,7 +2596,7 @@ function startWebServer(opts = {}) {
2495
2596
  if (!src.enabled || src.type !== 'templates')
2496
2597
  continue;
2497
2598
  try {
2498
- const r = await fetchWithRetry(src.url, 10000, 0);
2599
+ const r = await fetchWithRetry(src.url, 25000, 0);
2499
2600
  if (r.ok) {
2500
2601
  const data = await r.json();
2501
2602
  const list = Array.isArray(data) ? data : (data.items || data.templates || []);
@@ -2619,7 +2720,7 @@ function startWebServer(opts = {}) {
2619
2720
  if (!src.enabled || src.type !== 'office')
2620
2721
  continue;
2621
2722
  try {
2622
- const r = await fetchWithRetry(src.url, 10000, 0);
2723
+ const r = await fetchWithRetry(src.url, 25000, 0);
2623
2724
  if (r.ok) {
2624
2725
  const data = await r.json();
2625
2726
  const list = Array.isArray(data) ? data : (data.items || data.capabilities || []);
@@ -2738,6 +2839,22 @@ function startWebServer(opts = {}) {
2738
2839
  const list = loadModels().map(publicModel);
2739
2840
  res.json({ ok: true, models: list, defaultId: (list.find((m) => m.default) || {}).id || null });
2740
2841
  });
2842
+ // P1-3: 模型提供商聚合视图(按 apiBase 分组)
2843
+ app.get('/api/models/providers', (_req, res) => {
2844
+ const list = loadModels().map(publicModel);
2845
+ const byKey = new Map();
2846
+ for (const m of list) {
2847
+ const key = m.apiBase || 'custom';
2848
+ let entry = byKey.get(key);
2849
+ if (!entry) {
2850
+ entry = { providerId: key, apiBase: key, models: [], count: 0 };
2851
+ byKey.set(key, entry);
2852
+ }
2853
+ entry.models.push(m);
2854
+ entry.count++;
2855
+ }
2856
+ res.json({ ok: true, providers: Array.from(byKey.values()), total: list.length });
2857
+ });
2741
2858
  app.post('/api/models', (req, res) => {
2742
2859
  const body = req.body;
2743
2860
  const name = typeof body?.name === 'string' ? body.name.trim() : '';