feihong-code 0.6.1 → 7.2.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 (247) hide show
  1. package/.github/workflows/release.yml +173 -0
  2. package/CHANGELOG.md +83 -454
  3. package/dist/agent/browser-agent.js +354 -0
  4. package/dist/agent/browser-agent.js.map +1 -0
  5. package/dist/agent/change-manager.js +433 -0
  6. package/dist/agent/change-manager.js.map +1 -0
  7. package/dist/agent/code-rag.js +293 -0
  8. package/dist/agent/code-rag.js.map +1 -0
  9. package/dist/agent/code-writer.js +13 -1
  10. package/dist/agent/code-writer.js.map +1 -1
  11. package/dist/agent/completion-engine.js +522 -0
  12. package/dist/agent/completion-engine.js.map +1 -0
  13. package/dist/agent/context-compactor.js +188 -10
  14. package/dist/agent/context-compactor.js.map +1 -1
  15. package/dist/agent/custom-agent.js +368 -0
  16. package/dist/agent/custom-agent.js.map +1 -0
  17. package/dist/agent/design-to-code.js +218 -0
  18. package/dist/agent/design-to-code.js.map +1 -0
  19. package/dist/agent/event-driven-agent.js +359 -0
  20. package/dist/agent/event-driven-agent.js.map +1 -0
  21. package/dist/agent/git-integration.js +324 -0
  22. package/dist/agent/git-integration.js.map +1 -0
  23. package/dist/agent/layered-memory.js +329 -0
  24. package/dist/agent/layered-memory.js.map +1 -0
  25. package/dist/agent/multi-agent.js +359 -0
  26. package/dist/agent/multi-agent.js.map +1 -0
  27. package/dist/agent/orchestrator.js +160 -22
  28. package/dist/agent/orchestrator.js.map +1 -1
  29. package/dist/agent/prompts.js +20 -0
  30. package/dist/agent/prompts.js.map +1 -1
  31. package/dist/agent/quality-gate.js +139 -0
  32. package/dist/agent/quality-gate.js.map +1 -1
  33. package/dist/agent/self-correction.js +274 -0
  34. package/dist/agent/self-correction.js.map +1 -0
  35. package/dist/agent/self-heal.js +42 -12
  36. package/dist/agent/self-heal.js.map +1 -1
  37. package/dist/agent/solo-agent.js +321 -0
  38. package/dist/agent/solo-agent.js.map +1 -0
  39. package/dist/agent/symbol-index.js +258 -28
  40. package/dist/agent/symbol-index.js.map +1 -1
  41. package/dist/agent/team-collaboration.js +252 -0
  42. package/dist/agent/team-collaboration.js.map +1 -0
  43. package/dist/agent/type-checker.js +229 -0
  44. package/dist/agent/type-checker.js.map +1 -0
  45. package/dist/cli/run.js +1 -0
  46. package/dist/cli/run.js.map +1 -1
  47. package/dist/cli/version.js +1 -1
  48. package/dist/integrations/collaboration.js +219 -0
  49. package/dist/integrations/collaboration.js.map +1 -0
  50. package/dist/integrations/figma.js +307 -0
  51. package/dist/integrations/figma.js.map +1 -0
  52. package/dist/integrations/sso.js +327 -0
  53. package/dist/integrations/sso.js.map +1 -0
  54. package/dist/knowledge/library.js +301 -0
  55. package/dist/knowledge/library.js.map +1 -0
  56. package/dist/plugins/manager.js +332 -0
  57. package/dist/plugins/manager.js.map +1 -0
  58. package/dist/runtime/event-log.js.map +1 -1
  59. package/dist/tools/browser/browser.tool.js +217 -0
  60. package/dist/tools/browser/browser.tool.js.map +1 -0
  61. package/dist/tools/index.js +2 -0
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/tools/shell/exec.js +50 -10
  64. package/dist/tools/shell/exec.js.map +1 -1
  65. package/dist/tools/shell/run-shell.tool.js +8 -2
  66. package/dist/tools/shell/run-shell.tool.js.map +1 -1
  67. package/dist/training/completion-evaluator.js +213 -0
  68. package/dist/training/completion-evaluator.js.map +1 -0
  69. package/dist/training/fim-data.js +248 -0
  70. package/dist/training/fim-data.js.map +1 -0
  71. package/dist/voice/voice-programming.js +223 -0
  72. package/dist/voice/voice-programming.js.map +1 -0
  73. package/dist/web/public/css/style.css +190 -7
  74. package/dist/web/public/index.html +403 -9
  75. package/dist/web/public/js/api.js +191 -4
  76. package/dist/web/public/js/app.js +850 -123
  77. package/dist/web/public/js/mock-api.js +234 -0
  78. package/dist/web/public/js/monaco-editor.js +203 -0
  79. package/dist/web/public/js/ui.js +1271 -54
  80. package/dist/web/public/manifest.json +59 -0
  81. package/dist/web/public/service-worker.js +173 -0
  82. package/dist/web/public/vendor/monaco/vs/abap-D-t0cyap.js +1 -0
  83. package/dist/web/public/vendor/monaco/vs/apex-CcIm7xu6.js +1 -0
  84. package/dist/web/public/vendor/monaco/vs/assets/css.worker-URu8fCFR.js +97 -0
  85. package/dist/web/public/vendor/monaco/vs/assets/editor.worker-lj3bdIIn.js +30 -0
  86. package/dist/web/public/vendor/monaco/vs/assets/editorWebWorkerMain-CA_vMoUU.js +30 -0
  87. package/dist/web/public/vendor/monaco/vs/assets/html.worker-D1SL3iM8.js +474 -0
  88. package/dist/web/public/vendor/monaco/vs/assets/json.worker-CoJx_OPf.js +62 -0
  89. package/dist/web/public/vendor/monaco/vs/assets/ts.worker-BWKtMYOk.js +67722 -0
  90. package/dist/web/public/vendor/monaco/vs/azcli-BA0tQDCg.js +1 -0
  91. package/dist/web/public/vendor/monaco/vs/basic-languages/monaco.contribution.js +1 -0
  92. package/dist/web/public/vendor/monaco/vs/bat-C397hTD6.js +1 -0
  93. package/dist/web/public/vendor/monaco/vs/bicep-DF5aW17k.js +2 -0
  94. package/dist/web/public/vendor/monaco/vs/cameligo-plsz8qhj.js +1 -0
  95. package/dist/web/public/vendor/monaco/vs/clojure-Y2auQMzK.js +1 -0
  96. package/dist/web/public/vendor/monaco/vs/coffee-Bu45yuWE.js +1 -0
  97. package/dist/web/public/vendor/monaco/vs/cpp-CkKPQIni.js +1 -0
  98. package/dist/web/public/vendor/monaco/vs/csharp-CX28MZyh.js +1 -0
  99. package/dist/web/public/vendor/monaco/vs/csp-D8uWnyxW.js +1 -0
  100. package/dist/web/public/vendor/monaco/vs/css-CaeNmE3S.js +3 -0
  101. package/dist/web/public/vendor/monaco/vs/css.worker-CyhWkhHo.js +1 -0
  102. package/dist/web/public/vendor/monaco/vs/cssMode-CV6Ay48H.js +1 -0
  103. package/dist/web/public/vendor/monaco/vs/cypher-DVThT8BS.js +1 -0
  104. package/dist/web/public/vendor/monaco/vs/dart-CmGfCvrO.js +1 -0
  105. package/dist/web/public/vendor/monaco/vs/dockerfile-CZqqYdch.js +1 -0
  106. package/dist/web/public/vendor/monaco/vs/ecl-30fUercY.js +1 -0
  107. package/dist/web/public/vendor/monaco/vs/editor/editor.main.css +1 -0
  108. package/dist/web/public/vendor/monaco/vs/editor/editor.main.js +1 -0
  109. package/dist/web/public/vendor/monaco/vs/editor/editor.worker.js +1 -0
  110. package/dist/web/public/vendor/monaco/vs/editor-KLE6jdfb.js +831 -0
  111. package/dist/web/public/vendor/monaco/vs/editor.js +1 -0
  112. package/dist/web/public/vendor/monaco/vs/editorWorkerHost-fVE1cjcC.js +35 -0
  113. package/dist/web/public/vendor/monaco/vs/elixir-xjPaIfzF.js +1 -0
  114. package/dist/web/public/vendor/monaco/vs/flow9-DqtmStfK.js +1 -0
  115. package/dist/web/public/vendor/monaco/vs/freemarker2-FWreY7v7.js +3 -0
  116. package/dist/web/public/vendor/monaco/vs/fsharp-BOMdg4U1.js +1 -0
  117. package/dist/web/public/vendor/monaco/vs/go-D_hbi-Jt.js +1 -0
  118. package/dist/web/public/vendor/monaco/vs/graphql-CKUU4kLG.js +1 -0
  119. package/dist/web/public/vendor/monaco/vs/handlebars-CZi4pHCH.js +1 -0
  120. package/dist/web/public/vendor/monaco/vs/hcl-DTaboeZW.js +1 -0
  121. package/dist/web/public/vendor/monaco/vs/html-j_7ZTNRU.js +1 -0
  122. package/dist/web/public/vendor/monaco/vs/html.worker-CA3iAimZ.js +1 -0
  123. package/dist/web/public/vendor/monaco/vs/htmlMode-27by_KM4.js +1 -0
  124. package/dist/web/public/vendor/monaco/vs/index-049xrXrp.js +1 -0
  125. package/dist/web/public/vendor/monaco/vs/index-CBVt3dzv.js +5 -0
  126. package/dist/web/public/vendor/monaco/vs/index.js +1 -0
  127. package/dist/web/public/vendor/monaco/vs/ini-CsNwO04R.js +1 -0
  128. package/dist/web/public/vendor/monaco/vs/initialize-DL0l1TGY.js +1 -0
  129. package/dist/web/public/vendor/monaco/vs/java-CI4ZMsH9.js +1 -0
  130. package/dist/web/public/vendor/monaco/vs/javascript-DTJl4Jn1.js +1 -0
  131. package/dist/web/public/vendor/monaco/vs/json.worker-BizpAl9O.js +1 -0
  132. package/dist/web/public/vendor/monaco/vs/jsonMode-DLUiMUrI.js +1 -0
  133. package/dist/web/public/vendor/monaco/vs/julia-BwzEvaQw.js +1 -0
  134. package/dist/web/public/vendor/monaco/vs/kotlin-IUYPiTV8.js +1 -0
  135. package/dist/web/public/vendor/monaco/vs/language/css/css.worker.js +66 -0
  136. package/dist/web/public/vendor/monaco/vs/language/css/monaco.contribution.js +1 -0
  137. package/dist/web/public/vendor/monaco/vs/language/html/html.worker.js +443 -0
  138. package/dist/web/public/vendor/monaco/vs/language/html/monaco.contribution.js +1 -0
  139. package/dist/web/public/vendor/monaco/vs/language/json/json.worker.js +17 -0
  140. package/dist/web/public/vendor/monaco/vs/language/json/monaco.contribution.js +1 -0
  141. package/dist/web/public/vendor/monaco/vs/language/typescript/monaco.contribution.js +1 -0
  142. package/dist/web/public/vendor/monaco/vs/language/typescript/ts.worker.js +67693 -0
  143. package/dist/web/public/vendor/monaco/vs/less-C0eDYdqa.js +2 -0
  144. package/dist/web/public/vendor/monaco/vs/lexon-iON-Kj97.js +1 -0
  145. package/dist/web/public/vendor/monaco/vs/liquid-CLJYelW6.js +1 -0
  146. package/dist/web/public/vendor/monaco/vs/loader.js +1368 -0
  147. package/dist/web/public/vendor/monaco/vs/lspLanguageFeatures-BIkJOWLw.js +2 -0
  148. package/dist/web/public/vendor/monaco/vs/lua-DtygF91M.js +1 -0
  149. package/dist/web/public/vendor/monaco/vs/m3-CsR4AuFi.js +1 -0
  150. package/dist/web/public/vendor/monaco/vs/main-BEx-Fmlo.js +15 -0
  151. package/dist/web/public/vendor/monaco/vs/main-DsK8pnKg.js +3 -0
  152. package/dist/web/public/vendor/monaco/vs/markdown-C_rD0bIw.js +1 -0
  153. package/dist/web/public/vendor/monaco/vs/mdx-CpzZIPHF.js +1 -0
  154. package/dist/web/public/vendor/monaco/vs/mips-CiYP61RB.js +1 -0
  155. package/dist/web/public/vendor/monaco/vs/monaco.contribution-9cKT3C7t.js +1 -0
  156. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BE88ZNGY.js +1 -0
  157. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BPhsneLd.js +1 -0
  158. package/dist/web/public/vendor/monaco/vs/monaco.contribution-BgRy6xDf.js +1 -0
  159. package/dist/web/public/vendor/monaco/vs/msdax-C38-sJlp.js +1 -0
  160. package/dist/web/public/vendor/monaco/vs/mysql-CdtbpvbG.js +1 -0
  161. package/dist/web/public/vendor/monaco/vs/nls/lang/cs.d.ts +1 -0
  162. package/dist/web/public/vendor/monaco/vs/nls/lang/cs.js +9 -0
  163. package/dist/web/public/vendor/monaco/vs/nls/lang/de.d.ts +1 -0
  164. package/dist/web/public/vendor/monaco/vs/nls/lang/de.js +9 -0
  165. package/dist/web/public/vendor/monaco/vs/nls/lang/es.d.ts +1 -0
  166. package/dist/web/public/vendor/monaco/vs/nls/lang/es.js +9 -0
  167. package/dist/web/public/vendor/monaco/vs/nls/lang/fr.d.ts +1 -0
  168. package/dist/web/public/vendor/monaco/vs/nls/lang/fr.js +9 -0
  169. package/dist/web/public/vendor/monaco/vs/nls/lang/it.d.ts +1 -0
  170. package/dist/web/public/vendor/monaco/vs/nls/lang/it.js +9 -0
  171. package/dist/web/public/vendor/monaco/vs/nls/lang/ja.d.ts +1 -0
  172. package/dist/web/public/vendor/monaco/vs/nls/lang/ja.js +9 -0
  173. package/dist/web/public/vendor/monaco/vs/nls/lang/ko.d.ts +1 -0
  174. package/dist/web/public/vendor/monaco/vs/nls/lang/ko.js +9 -0
  175. package/dist/web/public/vendor/monaco/vs/nls/lang/pl.d.ts +1 -0
  176. package/dist/web/public/vendor/monaco/vs/nls/lang/pl.js +9 -0
  177. package/dist/web/public/vendor/monaco/vs/nls/lang/pt-br.d.ts +1 -0
  178. package/dist/web/public/vendor/monaco/vs/nls/lang/pt-br.js +9 -0
  179. package/dist/web/public/vendor/monaco/vs/nls/lang/ru.d.ts +1 -0
  180. package/dist/web/public/vendor/monaco/vs/nls/lang/ru.js +9 -0
  181. package/dist/web/public/vendor/monaco/vs/nls/lang/tr.d.ts +1 -0
  182. package/dist/web/public/vendor/monaco/vs/nls/lang/tr.js +9 -0
  183. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-cn.d.ts +1 -0
  184. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-cn.js +9 -0
  185. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-tw.d.ts +1 -0
  186. package/dist/web/public/vendor/monaco/vs/nls/lang/zh-tw.js +9 -0
  187. package/dist/web/public/vendor/monaco/vs/nls.messages-loader.js +1 -0
  188. package/dist/web/public/vendor/monaco/vs/objective-c-CntZFaHX.js +1 -0
  189. package/dist/web/public/vendor/monaco/vs/pascal-r6kuqfl_.js +1 -0
  190. package/dist/web/public/vendor/monaco/vs/pascaligo-BiXoTmXh.js +1 -0
  191. package/dist/web/public/vendor/monaco/vs/perl-DABw_TcH.js +1 -0
  192. package/dist/web/public/vendor/monaco/vs/pgsql-me_jFXeX.js +1 -0
  193. package/dist/web/public/vendor/monaco/vs/php-D_kh-9LK.js +1 -0
  194. package/dist/web/public/vendor/monaco/vs/pla-VfZjczW0.js +1 -0
  195. package/dist/web/public/vendor/monaco/vs/postiats-BBSzz8Pk.js +1 -0
  196. package/dist/web/public/vendor/monaco/vs/powerquery-Dt-g_2cc.js +1 -0
  197. package/dist/web/public/vendor/monaco/vs/powershell-B-7ap1zc.js +1 -0
  198. package/dist/web/public/vendor/monaco/vs/protobuf-BmtuEB1A.js +2 -0
  199. package/dist/web/public/vendor/monaco/vs/pug-BRpRNeEb.js +1 -0
  200. package/dist/web/public/vendor/monaco/vs/python-CqWUUgfu.js +1 -0
  201. package/dist/web/public/vendor/monaco/vs/qsharp-BzsFaUU9.js +1 -0
  202. package/dist/web/public/vendor/monaco/vs/r-f8dDdrp4.js +1 -0
  203. package/dist/web/public/vendor/monaco/vs/razor-hok5y9Km.js +1 -0
  204. package/dist/web/public/vendor/monaco/vs/redis-fvZQY4PI.js +1 -0
  205. package/dist/web/public/vendor/monaco/vs/redshift-45Et0LQi.js +1 -0
  206. package/dist/web/public/vendor/monaco/vs/restructuredtext-C7UUFKFD.js +1 -0
  207. package/dist/web/public/vendor/monaco/vs/ruby-CZO8zYTz.js +1 -0
  208. package/dist/web/public/vendor/monaco/vs/rust-Bfetafyc.js +1 -0
  209. package/dist/web/public/vendor/monaco/vs/sb-3GYllVck.js +1 -0
  210. package/dist/web/public/vendor/monaco/vs/scala-foMgrKo1.js +1 -0
  211. package/dist/web/public/vendor/monaco/vs/scheme-CHdMtr7p.js +1 -0
  212. package/dist/web/public/vendor/monaco/vs/scss-C1cmLt9V.js +3 -0
  213. package/dist/web/public/vendor/monaco/vs/shell-ClXCKCEW.js +1 -0
  214. package/dist/web/public/vendor/monaco/vs/solidity-MZ6ExpPy.js +1 -0
  215. package/dist/web/public/vendor/monaco/vs/sophia-DWkuSsPQ.js +1 -0
  216. package/dist/web/public/vendor/monaco/vs/sparql-AUGFYSyk.js +1 -0
  217. package/dist/web/public/vendor/monaco/vs/sql-32GpJSV2.js +1 -0
  218. package/dist/web/public/vendor/monaco/vs/st-CuDFIVZ_.js +1 -0
  219. package/dist/web/public/vendor/monaco/vs/swift-t-PfMj7W.js +1 -0
  220. package/dist/web/public/vendor/monaco/vs/systemverilog-Ch4vA8Yt.js +1 -0
  221. package/dist/web/public/vendor/monaco/vs/tcl-D74tq1nH.js +1 -0
  222. package/dist/web/public/vendor/monaco/vs/toggleHighContrast-qGX7E9o7.js +61 -0
  223. package/dist/web/public/vendor/monaco/vs/ts.worker-2QLmBukE.js +1 -0
  224. package/dist/web/public/vendor/monaco/vs/tsMode-B0S_kp2q.js +11 -0
  225. package/dist/web/public/vendor/monaco/vs/twig-C6taOxMV.js +1 -0
  226. package/dist/web/public/vendor/monaco/vs/typescript-_2t_511t.js +1 -0
  227. package/dist/web/public/vendor/monaco/vs/typespec-8OfoLt6R.js +1 -0
  228. package/dist/web/public/vendor/monaco/vs/vb-Dyb2648j.js +1 -0
  229. package/dist/web/public/vendor/monaco/vs/wgsl-BhLXMOR0.js +298 -0
  230. package/dist/web/public/vendor/monaco/vs/workers-BBttULjf.js +1 -0
  231. package/dist/web/public/vendor/monaco/vs/xml-cZjNDqDT.js +1 -0
  232. package/dist/web/public/vendor/monaco/vs/yaml-A1fOIdH6.js +1 -0
  233. package/dist/web/server.js +2073 -210
  234. package/dist/web/server.js.map +1 -1
  235. package/dist/web/task-queue.js +25 -5
  236. package/dist/web/task-queue.js.map +1 -1
  237. package/docs/BENCHMARK_REPORT_en.md +329 -0
  238. package/docs/BENCHMARK_REPORT_zh.md +329 -0
  239. package/docs/DEPLOYMENT.md +382 -0
  240. package/docs/MODEL_FINETUNING.md +405 -0
  241. package/docs/TECHNICAL_SPEC_en.md +292 -0
  242. package/docs/TECHNICAL_SPEC_zh.md +292 -0
  243. package/docs/UPDATE_NOTES_en.md +159 -0
  244. package/docs/UPDATE_NOTES_zh.md +159 -0
  245. package/electron/main.js +602 -0
  246. package/package.json +67 -3
  247. package/tool-schema.json +1 -1
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -29,6 +62,19 @@ const version_1 = require("../cli/version");
29
62
  const i18n_1 = require("../shared/i18n");
30
63
  const enterprise_1 = require("../enterprise");
31
64
  const config_1 = require("../shared/config");
65
+ const model_router_1 = require("../models/model-router");
66
+ const openai_compatible_provider_1 = require("../models/providers/openai-compatible.provider");
67
+ const completion_engine_1 = require("../agent/completion-engine");
68
+ const symbol_index_1 = require("../agent/symbol-index");
69
+ const change_manager_1 = require("../agent/change-manager");
70
+ const design_to_code_1 = require("../agent/design-to-code");
71
+ const git_integration_1 = require("../agent/git-integration");
72
+ const team_collaboration_1 = require("../agent/team-collaboration");
73
+ const solo_agent_1 = require("../agent/solo-agent");
74
+ const multi_agent_1 = require("../agent/multi-agent");
75
+ const event_driven_agent_1 = require("../agent/event-driven-agent");
76
+ const custom_agent_1 = require("../agent/custom-agent");
77
+ const mcp_client_1 = require("../tools/mcp/mcp-client");
32
78
  const memory_1 = require("../memory");
33
79
  const auto_summarize_1 = require("../memory/auto-summarize");
34
80
  const secure_store_1 = require("../shared/secure-store");
@@ -58,6 +104,22 @@ function startWebServer(opts = {}) {
58
104
  const sessions = new auth_1.SessionStore();
59
105
  // 当前 Web 控制台工作区,任务提交缺省时使用
60
106
  let serverWorkspaceDir = (0, path_1.resolve)(process.cwd());
107
+ // P3: 多文件变更管理器(AI 生成的修改先暂存,用户审批后才写入磁盘)
108
+ const changeManager = new change_manager_1.ChangeManager({ cwd: serverWorkspaceDir });
109
+ // P2-2: Git 集成管理器
110
+ let gitIntegration = (0, git_integration_1.createGitIntegration)(serverWorkspaceDir);
111
+ // P2-3: 团队协作管理器
112
+ const teamManager = (0, team_collaboration_1.createTeamCollaborationManager)();
113
+ // 阶段二-2: 事件驱动 Agent 管理器
114
+ const eventDrivenManager = (0, event_driven_agent_1.createEventDrivenAgentManager)(serverWorkspaceDir, async (event) => {
115
+ console.log('[event-driven] processing:', { eventId: event.id, type: event.type });
116
+ // 实际处理逻辑:根据事件类型触发对应的 Agent 任务
117
+ // 这里简化处理,实际应集成到 orchestrator
118
+ });
119
+ // 阶段二-3: 自定义 Agent 管理器
120
+ const customAgentManager = (0, custom_agent_1.createCustomAgentManager)(serverWorkspaceDir);
121
+ // P3-2: SOLO 全自主编程任务存储
122
+ const soloTasks = new Map();
61
123
  // 任务队列(进程内;服务端静默执行)— 需在登录接口前初始化
62
124
  const persistDir = process.env.FH_TASK_PERSIST_DIR?.trim() ||
63
125
  (0, path_1.join)(process.env.FH_HOME?.trim() || (0, path_1.join)(require('os').homedir(), '.feihong-code'), 'tasks');
@@ -65,6 +127,13 @@ function startWebServer(opts = {}) {
65
127
  concurrency: Number(process.env.FH_TASK_CONCURRENCY ?? 2),
66
128
  webhookUrl: process.env.FH_TASK_WEBHOOK_URL,
67
129
  persistDir,
130
+ // P3-1: AI 生成的文件修改自动暂存到变更面板
131
+ stageChange: (path, content) => {
132
+ try {
133
+ changeManager.stageChange(path, content);
134
+ }
135
+ catch (e) { /* 暂存失败不影响任务执行 */ }
136
+ },
68
137
  });
69
138
  // 公开健康检查(仅暴露版本/状态等观测信息,无敏感数据)
70
139
  app.get('/api/health', (_req, res) => {
@@ -129,10 +198,58 @@ function startWebServer(opts = {}) {
129
198
  const content = (0, memory_1.readShortTerm)(memoryConfig, date ? new Date(date) : undefined);
130
199
  res.json({ ok: true, content, date: date || new Date().toISOString().split('T')[0] });
131
200
  });
201
+ // 手动添加一条短期记忆
202
+ app.post('/api/memory/short', (req, res) => {
203
+ const body = (req.body ?? {});
204
+ const type = ['task', 'fix', 'feature', 'error', 'note'].find((t) => t === body.type) || 'note';
205
+ const title = typeof body.title === 'string' && body.title.trim() ? body.title.trim() : '';
206
+ const content = typeof body.content === 'string' && body.content.trim() ? body.content.trim() : '';
207
+ if (!title || !content) {
208
+ res.status(400).json({ ok: false, error: '缺少 title 或 content 字段' });
209
+ return;
210
+ }
211
+ try {
212
+ const path = (0, memory_1.appendShortTerm)(memoryConfig, { type, title, content });
213
+ res.json({ ok: true, path, message: '已添加到短期记忆' });
214
+ }
215
+ catch (e) {
216
+ res.status(500).json({ ok: false, error: '添加失败: ' + e.message });
217
+ }
218
+ });
132
219
  app.get('/api/memory/long', (_req, res) => {
133
220
  const content = (0, memory_1.readLongTerm)(memoryConfig);
134
221
  res.json({ ok: true, content });
135
222
  });
223
+ // 写入/编辑长期记忆(用户自定义需要记忆的内容)
224
+ app.post('/api/memory/long', (req, res) => {
225
+ const body = (req.body ?? {});
226
+ const content = typeof body.content === 'string' ? body.content : '';
227
+ try {
228
+ (0, memory_1.writeLongTerm)(memoryConfig, content);
229
+ res.json({ ok: true, message: '长期记忆已保存' });
230
+ }
231
+ catch (e) {
232
+ res.status(500).json({ ok: false, error: '保存失败: ' + e.message });
233
+ }
234
+ });
235
+ // 追加一条长期记忆
236
+ app.post('/api/memory/long/append', (req, res) => {
237
+ const body = (req.body ?? {});
238
+ const category = typeof body.category === 'string' && body.category.trim() ? body.category.trim() : '自定义';
239
+ const title = typeof body.title === 'string' && body.title.trim() ? body.title.trim() : '';
240
+ const content = typeof body.content === 'string' && body.content.trim() ? body.content.trim() : '';
241
+ if (!title || !content) {
242
+ res.status(400).json({ ok: false, error: '缺少 title 或 content 字段' });
243
+ return;
244
+ }
245
+ try {
246
+ const id = (0, memory_1.appendLongTerm)(memoryConfig, { category, title, content, summarizedFrom: '手动添加' });
247
+ res.json({ ok: true, id, message: '已追加到长期记忆' });
248
+ }
249
+ catch (e) {
250
+ res.status(500).json({ ok: false, error: '追加失败: ' + e.message });
251
+ }
252
+ });
136
253
  app.get('/api/memory/stats', (_req, res) => {
137
254
  const stats = (0, memory_1.getMemoryStats)(memoryConfig);
138
255
  res.json({ ok: true, ...stats });
@@ -402,6 +519,70 @@ function startWebServer(opts = {}) {
402
519
  res.status(500).json({ ok: false, error: '读取目录失败: ' + e.message });
403
520
  }
404
521
  });
522
+ // 新建文件夹
523
+ app.post('/api/workspace/mkdir', (req, res) => {
524
+ const body = (req.body ?? {});
525
+ const parent = typeof body?.parent === 'string' ? body.parent.trim() : '';
526
+ const name = typeof body?.name === 'string' ? body.name.trim() : '';
527
+ if (!parent || !name) {
528
+ res.status(400).json({ ok: false, error: '缺少 parent 或 name 字段' });
529
+ return;
530
+ }
531
+ // 文件夹名安全校验:禁止路径分隔符和特殊字符
532
+ if (/[\\/:*?"<>|]/.test(name)) {
533
+ res.status(400).json({ ok: false, error: '文件夹名包含非法字符' });
534
+ return;
535
+ }
536
+ const parentDir = (0, path_1.resolve)(parent);
537
+ if (!assertPathAllowed(parentDir, res))
538
+ return;
539
+ const newDir = (0, path_1.join)(parentDir, name);
540
+ try {
541
+ if ((0, fs_1.existsSync)(newDir)) {
542
+ res.status(409).json({ ok: false, error: '文件夹已存在' });
543
+ return;
544
+ }
545
+ (0, fs_1.mkdirSync)(newDir, { recursive: true });
546
+ res.json({ ok: true, path: newDir });
547
+ }
548
+ catch (e) {
549
+ res.status(500).json({ ok: false, error: '创建文件夹失败: ' + e.message });
550
+ }
551
+ });
552
+ // 重命名文件夹
553
+ app.post('/api/workspace/rename', (req, res) => {
554
+ const body = (req.body ?? {});
555
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
556
+ const newName = typeof body?.newName === 'string' ? body.newName.trim() : '';
557
+ if (!path || !newName) {
558
+ res.status(400).json({ ok: false, error: '缺少 path 或 newName 字段' });
559
+ return;
560
+ }
561
+ if (/[\\/:*?"<>|]/.test(newName)) {
562
+ res.status(400).json({ ok: false, error: '文件夹名包含非法字符' });
563
+ return;
564
+ }
565
+ const oldPath = (0, path_1.resolve)(path);
566
+ if (!assertPathAllowed(oldPath, res))
567
+ return;
568
+ if (!(0, fs_1.existsSync)(oldPath) || !(0, fs_1.statSync)(oldPath).isDirectory()) {
569
+ res.status(400).json({ ok: false, error: '目标不是文件夹或不存在' });
570
+ return;
571
+ }
572
+ const parentDir = (0, path_1.dirname)(oldPath);
573
+ const newPath = (0, path_1.join)(parentDir, newName);
574
+ try {
575
+ if ((0, fs_1.existsSync)(newPath)) {
576
+ res.status(409).json({ ok: false, error: '同名文件夹已存在' });
577
+ return;
578
+ }
579
+ (0, fs_1.renameSync)(oldPath, newPath);
580
+ res.json({ ok: true, path: newPath });
581
+ }
582
+ catch (e) {
583
+ res.status(500).json({ ok: false, error: '重命名失败: ' + e.message });
584
+ }
585
+ });
405
586
  app.post('/api/files/read', (req, res) => {
406
587
  const body = (req.body ?? {});
407
588
  const file = typeof body?.path === 'string' ? body.path.trim() : '';
@@ -424,207 +605,1752 @@ function startWebServer(opts = {}) {
424
605
  res.status(500).json({ ok: false, error: '读取失败: ' + e.message });
425
606
  }
426
607
  });
427
- /* ========== 打开本地文件夹/浏览器 ========== */
428
- app.post('/api/open/folder', (req, res) => {
608
+ /* ========== P3: 多文件变更管理(暂存/审批/原子提交) ========== */
609
+ // 暂存变更(AI 生成的文件内容先暂存,不直接写入)
610
+ app.post('/api/changes/stage', (req, res) => {
429
611
  const body = (req.body ?? {});
430
- const dir = typeof body?.path === 'string' ? body.path.trim() : serverWorkspaceDir;
431
- if (!dir || !assertPathAllowed(dir, res))
612
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
613
+ const content = typeof body?.content === 'string' ? body.content : '';
614
+ if (!path) {
615
+ res.status(400).json({ ok: false, error: '缺少 path 字段' });
432
616
  return;
433
- try {
434
- openFolder(dir);
435
- res.json({ ok: true });
436
617
  }
437
- catch (e) {
438
- res.status(500).json({ ok: false, error: '打开失败: ' + e.message });
618
+ const change = changeManager.stageChange(path, content);
619
+ if (!change) {
620
+ res.status(400).json({ ok: false, error: '内容未变化或暂存区已满' });
621
+ return;
439
622
  }
623
+ res.json({ ok: true, change: { path: change.path, type: change.type, additions: change.additions, deletions: change.deletions, status: change.status, hunks: change.hunks } });
440
624
  });
441
- app.post('/api/open/browser', (req, res) => {
625
+ // 获取所有暂存变更(变更面板数据)
626
+ app.get('/api/changes', (_req, res) => {
627
+ res.json({ ok: true, ...changeManager.toPanelData() });
628
+ });
629
+ // 接受整个文件的变更
630
+ app.post('/api/changes/accept', (req, res) => {
442
631
  const body = (req.body ?? {});
443
- const url = typeof body?.url === 'string' ? body.url.trim() : '';
444
- if (!url) {
445
- res.status(400).json({ ok: false, error: '缺少 url 字段' });
632
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
633
+ if (!path) {
634
+ res.status(400).json({ ok: false, error: '缺少 path 字段' });
635
+ return;
636
+ }
637
+ const ok = changeManager.acceptFile(path);
638
+ res.json({ ok, ...(ok ? {} : { error: '文件不存在' }) });
639
+ });
640
+ // 拒绝整个文件的变更
641
+ app.post('/api/changes/reject', (req, res) => {
642
+ const body = (req.body ?? {});
643
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
644
+ if (!path) {
645
+ res.status(400).json({ ok: false, error: '缺少 path 字段' });
646
+ return;
647
+ }
648
+ const ok = changeManager.rejectFile(path);
649
+ res.json({ ok, ...(ok ? {} : { error: '文件不存在' }) });
650
+ });
651
+ // 接受单个 Hunk
652
+ app.post('/api/changes/hunk/accept', (req, res) => {
653
+ const body = (req.body ?? {});
654
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
655
+ const hunkIndex = typeof body?.hunkIndex === 'number' ? body.hunkIndex : -1;
656
+ if (!path || hunkIndex < 0) {
657
+ res.status(400).json({ ok: false, error: '缺少 path 或 hunkIndex' });
658
+ return;
659
+ }
660
+ const ok = changeManager.acceptHunk(path, hunkIndex);
661
+ res.json({ ok, ...(ok ? {} : { error: '文件或 Hunk 不存在' }) });
662
+ });
663
+ // 拒绝单个 Hunk
664
+ app.post('/api/changes/hunk/reject', (req, res) => {
665
+ const body = (req.body ?? {});
666
+ const path = typeof body?.path === 'string' ? body.path.trim() : '';
667
+ const hunkIndex = typeof body?.hunkIndex === 'number' ? body.hunkIndex : -1;
668
+ if (!path || hunkIndex < 0) {
669
+ res.status(400).json({ ok: false, error: '缺少 path 或 hunkIndex' });
446
670
  return;
447
671
  }
672
+ const ok = changeManager.rejectHunk(path, hunkIndex);
673
+ res.json({ ok, ...(ok ? {} : { error: '文件或 Hunk 不存在' }) });
674
+ });
675
+ // 原子化提交(写入所有已接受的变更,失败自动回滚)
676
+ app.post('/api/changes/commit', (_req, res) => {
677
+ const result = changeManager.commit();
678
+ res.json({ ok: result.success, ...result });
679
+ });
680
+ // 丢弃所有暂存变更
681
+ app.post('/api/changes/discard', (_req, res) => {
682
+ changeManager.discardAll();
683
+ res.json({ ok: true });
684
+ });
685
+ // 检测冲突
686
+ app.post('/api/changes/detect-conflicts', (_req, res) => {
687
+ const conflicts = changeManager.detectConflicts();
688
+ res.json({ ok: true, conflicts });
689
+ });
690
+ /* ========== P1-2: MCP 协议管理 API ========== */
691
+ // 获取当前 MCP 服务器配置
692
+ app.get('/api/mcp/config', (_req, res) => {
448
693
  try {
449
- openBrowser(url);
450
- res.json({ ok: true });
694
+ const cfg = (0, config_1.loadConfig)();
695
+ const servers = (cfg.mcp?.servers ?? []).map((s) => ({
696
+ name: s.name,
697
+ command: s.command,
698
+ args: s.args ?? [],
699
+ env: s.env ? Object.keys(s.env) : [], // 只暴露环境变量名,不暴露值
700
+ }));
701
+ res.json({ ok: true, servers, count: servers.length });
451
702
  }
452
703
  catch (e) {
453
- res.status(500).json({ ok: false, error: '打开失败: ' + e.message });
704
+ res.status(500).json({ ok: false, error: `读取配置失败: ${e instanceof Error ? e.message : String(e)}` });
454
705
  }
455
706
  });
456
- /* ========== 上传文件/图片 ========== */
457
- const uploadsDir = () => (0, path_1.join)(homeDir, 'uploads');
458
- app.post('/api/upload', (req, res) => {
707
+ // 测试指定 MCP 服务器连接(临时连接,列出工具后关闭)
708
+ app.post('/api/mcp/test', async (req, res) => {
459
709
  const body = (req.body ?? {});
460
710
  const name = typeof body?.name === 'string' ? body.name.trim() : '';
461
- const mime = typeof body?.mime === 'string' ? body.mime.trim() : 'application/octet-stream';
462
- const data = typeof body?.dataBase64 === 'string' ? body.dataBase64.trim() : '';
463
- if (!name || !data) {
464
- res.status(400).json({ ok: false, error: '缺少 name 或 dataBase64 字段' });
711
+ if (!name) {
712
+ res.status(400).json({ ok: false, error: '缺少 name 字段' });
465
713
  return;
466
714
  }
467
715
  try {
468
- (0, fs_1.mkdirSync)(uploadsDir(), { recursive: true });
469
- const safeName = name.replace(/[^a-zA-Z0-9_.\-]/g, '_');
470
- const dest = (0, path_1.join)(uploadsDir(), `${Date.now()}_${safeName}`);
471
- (0, fs_1.writeFileSync)(dest, Buffer.from(data, 'base64'));
472
- res.json({ ok: true, path: dest, name, mime });
716
+ const cfg = (0, config_1.loadConfig)();
717
+ const serverCfg = (cfg.mcp?.servers ?? []).find((s) => s.name === name);
718
+ if (!serverCfg) {
719
+ res.status(404).json({ ok: false, error: `未找到 MCP 服务器: ${name}` });
720
+ return;
721
+ }
722
+ const startTime = Date.now();
723
+ const client = await (0, mcp_client_1.connectMcp)(serverCfg);
724
+ const tools = await client.listTools();
725
+ const latencyMs = Date.now() - startTime;
726
+ await client.close().catch(() => undefined);
727
+ res.json({
728
+ ok: true,
729
+ name,
730
+ connected: true,
731
+ latencyMs,
732
+ toolCount: tools.length,
733
+ tools: tools.map((t) => ({
734
+ name: t.name,
735
+ description: (t.description || '').slice(0, 100),
736
+ })),
737
+ });
473
738
  }
474
739
  catch (e) {
475
- res.status(500).json({ ok: false, error: '上传失败: ' + e.message });
740
+ res.json({
741
+ ok: false,
742
+ name,
743
+ connected: false,
744
+ error: `连接失败: ${e instanceof Error ? e.message : String(e)}`,
745
+ });
476
746
  }
477
747
  });
478
- /* ========== 技能市场(插件市场):聚合 ClawHub + Agent-Foundry ========== */
479
- const CLAWHUB_API = 'https://clawhub.ai/api/v1/skills';
480
- const AGENT_FOUNDRY_CATALOG = 'https://raw.githubusercontent.com/hebertzhu/agent-foundry/main/catalog/skills-catalog.json';
481
- async function fetchWithRetry(url, timeoutMs = 15000, retries = 1) {
482
- let lastErr;
483
- for (let attempt = 0; attempt <= retries; attempt++) {
484
- const ctrl = new AbortController();
485
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
486
- try {
487
- const r = await fetch(url, {
488
- signal: ctrl.signal,
489
- headers: { 'User-Agent': 'fhcode-web/0.5.1' },
490
- });
491
- clearTimeout(timer);
492
- return r;
493
- }
494
- catch (e) {
495
- clearTimeout(timer);
496
- lastErr = e;
497
- if (attempt < retries)
498
- await new Promise((res) => setTimeout(res, 500));
499
- }
500
- }
501
- throw lastErr;
502
- }
503
- async function fetchClawHubSkills(keyword, limit) {
748
+ // 列出所有 MCP 服务器的工具(逐个连接测试,返回汇总)
749
+ app.get('/api/mcp/tools', async (_req, res) => {
504
750
  try {
505
- const url = `${CLAWHUB_API}?limit=${limit}${keyword ? `&q=${encodeURIComponent(keyword)}` : ''}`;
506
- const r = await fetchWithRetry(url);
507
- if (!r.ok)
508
- return [];
509
- const data = (await r.json());
510
- return (data.items || []).map((it) => ({
511
- id: `clawhub:${String(it.slug || '')}`,
512
- name: String(it.displayName || it.slug || ''),
513
- description: String(it.summary || it.description || ''),
514
- source: 'clawhub',
515
- author: it.slug ? String(it.slug).split('/')[0] : undefined,
516
- category: 'market',
517
- tags: Array.isArray(it.topics) ? it.topics : [],
518
- downloads: Number(it.stats?.downloads ?? 0),
519
- installHint: `npx skills add ${String(it.slug || '')}`,
520
- homepage: `https://clawhub.ai/${String(it.slug || '')}`,
521
- rawUrl: `https://clawhub.ai/${String(it.slug || '')}`,
522
- }));
751
+ const cfg = (0, config_1.loadConfig)();
752
+ const servers = cfg.mcp?.servers ?? [];
753
+ const results = [];
754
+ for (const s of servers) {
755
+ try {
756
+ const client = await (0, mcp_client_1.connectMcp)(s);
757
+ const tools = await client.listTools();
758
+ await client.close().catch(() => undefined);
759
+ results.push({ name: s.name, ok: true, toolCount: tools.length });
760
+ }
761
+ catch (e) {
762
+ results.push({ name: s.name, ok: false, toolCount: 0, error: e instanceof Error ? e.message : String(e) });
763
+ }
764
+ }
765
+ res.json({ ok: true, servers: results, totalServers: servers.length });
523
766
  }
524
767
  catch (e) {
525
- console.error('[clawhub] fetch failed:', e);
526
- return [];
768
+ res.status(500).json({ ok: false, error: `获取 MCP 工具失败: ${e instanceof Error ? e.message : String(e)}` });
769
+ }
770
+ });
771
+ /* ========== P2: 代码补全 API(真实接入 CompletionEngine) ========== */
772
+ app.post('/api/completion', async (req, res) => {
773
+ const body = (req.body ?? {});
774
+ const filePath = typeof body?.filePath === 'string' ? body.filePath.trim() : '';
775
+ const fileContent = typeof body?.fileContent === 'string' ? body.fileContent : '';
776
+ const cursorOffset = typeof body?.cursorOffset === 'number' ? body.cursorOffset : fileContent.length;
777
+ const mode = body?.mode === 'full' ? 'full' : 'quick';
778
+ const language = typeof body?.language === 'string' ? body.language : undefined;
779
+ if (!filePath || !fileContent) {
780
+ res.status(400).json({ ok: false, error: '缺少 filePath 或 fileContent' });
781
+ return;
782
+ }
783
+ if (!completionEngine) {
784
+ res.json({ ok: true, suggestions: [], latencyMs: 0, model: '', cached: false, note: '未配置模型,补全不可用' });
785
+ return;
527
786
  }
528
- }
529
- async function fetchAgentFoundrySkills(keyword, limit) {
530
787
  try {
531
- const r = await fetchWithRetry(AGENT_FOUNDRY_CATALOG);
532
- if (!r.ok)
533
- return [];
534
- const data = (await r.json());
535
- const arr = Array.isArray(data.skills) ? data.skills : [];
536
- let list = arr.map((it) => {
537
- const name = String(it.name || '');
538
- const publicPath = String(it.public_path || '');
539
- const category = String(it.category || 'market');
540
- const rawUrl = publicPath
541
- ? `https://raw.githubusercontent.com/hebertzhu/agent-foundry/main/${publicPath}`
542
- : `https://github.com/hebertzhu/agent-foundry`;
543
- return {
544
- id: `agent-foundry:${name}`,
545
- name,
546
- description: `Agent-Foundry 技能包 · 分类:${category}${publicPath ? ` · 源:${publicPath}` : ''}`,
547
- source: 'agent-foundry',
548
- author: 'hebertzhu',
549
- category,
550
- tags: Array.isArray(it.packs) ? it.packs : [category],
551
- downloads: 0,
552
- installHint: `npm i -g @agent-foundry/cli && agent-foundry install ${name}`,
553
- homepage: `https://github.com/hebertzhu/agent-foundry`,
554
- rawUrl,
555
- };
788
+ const result = await completionEngine.complete({
789
+ filePath,
790
+ fileContent,
791
+ cursorOffset,
792
+ mode,
793
+ language,
556
794
  });
557
- if (keyword) {
558
- const kw = keyword.toLowerCase();
559
- list = list.filter((s) => s.name.toLowerCase().includes(kw) ||
560
- s.description.toLowerCase().includes(kw) ||
561
- (s.tags || []).some((t) => t.toLowerCase().includes(kw)));
562
- }
563
- return list.slice(0, limit);
795
+ res.json({ ok: true, ...result });
564
796
  }
565
797
  catch (e) {
566
- console.error('[agent-foundry] fetch failed:', e);
567
- return [];
798
+ res.status(500).json({ ok: false, error: '补全失败: ' + e.message, suggestions: [] });
568
799
  }
569
- }
570
- app.get('/api/skills/market', async (req, res) => {
571
- const q = typeof req.query.q === 'string' ? req.query.q.trim() : '';
572
- const source = typeof req.query.source === 'string' ? req.query.source : 'all';
573
- const limit = Math.min(Number(req.query.limit) || 50, 200);
574
- let results = [];
575
- if (source === 'clawhub' || source === 'all')
576
- results = results.concat(await fetchClawHubSkills(q, limit));
577
- if (source === 'agent-foundry' || source === 'all')
578
- results = results.concat(await fetchAgentFoundrySkills(q, limit));
579
- results.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
580
- res.json({ ok: true, total: results.length, skills: results.slice(0, limit) });
581
800
  });
582
- // 已安装技能持久化
583
- const installedSkillsFile = (0, path_1.join)(homeDir, 'skills', 'installed.json');
584
- app.post('/api/skills/install', async (req, res) => {
585
- const body = req.body;
586
- if (!body || !body.id || !body.name || !body.source) {
587
- res.status(400).json({ ok: false, error: '缺少 id / name / source 字段' });
801
+ /* ========== 阶段一-2:补全 Pro API ========== */
802
+ // 记录用户接受的补全(用于连续推荐)
803
+ app.post('/api/completion/accept', (req, res) => {
804
+ const body = (req.body ?? {});
805
+ const filePath = typeof body?.filePath === 'string' ? body.filePath.trim() : '';
806
+ const cursorOffset = typeof body?.cursorOffset === 'number' ? body.cursorOffset : 0;
807
+ const text = typeof body?.text === 'string' ? body.text : '';
808
+ if (!filePath || !text) {
809
+ res.status(400).json({ ok: false, error: '缺少 filePath 或 text' });
810
+ return;
811
+ }
812
+ if (!completionEngine) {
813
+ res.json({ ok: false, error: '补全引擎未初始化' });
588
814
  return;
589
815
  }
590
816
  try {
591
- const dir = (0, path_1.join)(homeDir, 'skills', body.id.replace(/[^a-zA-Z0-9_-]/g, '_'));
592
- (0, fs_1.mkdirSync)(dir, { recursive: true });
593
- if (!saveJsonFile((0, path_1.join)(dir, 'meta.json'), { ...body, installedAt: new Date().toISOString() })) {
594
- res.status(500).json({ ok: false, error: '技能元数据写入失败' });
595
- return;
596
- }
597
- const installed = loadJsonFile(installedSkillsFile, []);
598
- if (!installed.find((s) => s.id === body.id)) {
599
- installed.push(body);
600
- if (!saveJsonFile(installedSkillsFile, installed)) {
601
- res.status(500).json({ ok: false, error: '已安装列表写入失败' });
602
- return;
603
- }
604
- }
605
- res.json({ ok: true, message: `技能「${body.name}」已记录为已安装` });
817
+ completionEngine.recordAcceptance(filePath, cursorOffset, text);
818
+ res.json({ ok: true });
606
819
  }
607
820
  catch (e) {
608
- res.status(500).json({ ok: false, error: '安装失败: ' + e.message });
821
+ res.status(500).json({ ok: false, error: e.message });
609
822
  }
610
823
  });
611
- app.get('/api/skills/installed', (_req, res) => {
612
- res.json({ ok: true, skills: loadJsonFile(installedSkillsFile, []) });
613
- });
614
- const automationsFile = (0, path_1.join)(homeDir, 'automations.json');
615
- app.get('/api/automations', (_req, res) => {
616
- res.json({ ok: true, automations: loadJsonFile(automationsFile, []) });
617
- });
618
- app.post('/api/automations', async (req, res) => {
824
+ // 推荐下一个改动点(补全 Pro 连续推荐)
825
+ app.post('/api/completion/suggest-next', (req, res) => {
619
826
  const body = (req.body ?? {});
620
- const name = typeof body.name === 'string' ? body.name.trim() : '';
621
- const goal = typeof body.goal === 'string' ? body.goal.trim() : '';
622
- if (!name || !goal) {
623
- res.status(400).json({ ok: false, error: '缺少 name 或 goal 字段' });
827
+ const filePath = typeof body?.filePath === 'string' ? body.filePath.trim() : '';
828
+ const fileContent = typeof body?.fileContent === 'string' ? body.fileContent : '';
829
+ const cursorOffset = typeof body?.cursorOffset === 'number' ? body.cursorOffset : fileContent.length;
830
+ if (!filePath || !fileContent) {
831
+ res.status(400).json({ ok: false, error: '缺少 filePath 或 fileContent' });
624
832
  return;
625
833
  }
626
- const list = loadJsonFile(automationsFile, []);
627
- const rule = {
834
+ if (!completionEngine) {
835
+ res.json({ ok: true, suggested: false });
836
+ return;
837
+ }
838
+ try {
839
+ const suggestion = completionEngine.suggestNext(filePath, fileContent, cursorOffset);
840
+ res.json({ ok: true, ...suggestion });
841
+ }
842
+ catch (e) {
843
+ res.status(500).json({ ok: false, error: e.message });
844
+ }
845
+ });
846
+ /* ========== P2-1: 设计稿转代码 API(多模态,图片→代码) ========== */
847
+ app.post('/api/design-to-code', async (req, res) => {
848
+ const body = (req.body ?? {});
849
+ const image = typeof body?.image === 'string' ? body.image.trim() : '';
850
+ const framework = body?.framework || 'html';
851
+ const instructions = typeof body?.instructions === 'string' ? body.instructions : undefined;
852
+ const previousCode = typeof body?.previousCode === 'string' ? body.previousCode : undefined;
853
+ const feedback = typeof body?.feedback === 'string' ? body.feedback : undefined;
854
+ if (!image) {
855
+ res.status(400).json({ ok: false, error: '缺少 image 字段(base64 或 URL)' });
856
+ return;
857
+ }
858
+ if (!designToCodeEngine) {
859
+ res.status(503).json({ ok: false, error: '未配置多模态模型。请在模型设置中添加支持 vision 能力的模型(如 GPT-4V、Claude 3、通义千问 VL 等)。' });
860
+ return;
861
+ }
862
+ try {
863
+ const result = await designToCodeEngine.generate({
864
+ image,
865
+ framework,
866
+ instructions,
867
+ previousCode,
868
+ feedback,
869
+ });
870
+ res.json({ ok: true, ...result });
871
+ }
872
+ catch (e) {
873
+ res.status(500).json({ ok: false, error: e.message });
874
+ }
875
+ });
876
+ /* ========== P2-2: Git 集成 API ========== */
877
+ // 获取 Git 状态
878
+ app.get('/api/git/status', async (_req, res) => {
879
+ try {
880
+ const status = await gitIntegration.status();
881
+ res.json({ ok: true, ...status });
882
+ }
883
+ catch (e) {
884
+ res.status(500).json({ ok: false, error: e.message });
885
+ }
886
+ });
887
+ // 获取文件 diff
888
+ app.post('/api/git/diff', async (req, res) => {
889
+ const body = (req.body ?? {});
890
+ const path = typeof body?.path === 'string' ? body.path : undefined;
891
+ const staged = body?.staged === true;
892
+ try {
893
+ const diffs = await gitIntegration.diff(path, staged);
894
+ res.json({ ok: true, diffs });
895
+ }
896
+ catch (e) {
897
+ res.status(500).json({ ok: false, error: e.message });
898
+ }
899
+ });
900
+ // 暂存文件
901
+ app.post('/api/git/add', async (req, res) => {
902
+ const body = (req.body ?? {});
903
+ const paths = Array.isArray(body?.paths) ? body.paths.filter((p) => typeof p === 'string') : [];
904
+ if (!paths.length) {
905
+ res.status(400).json({ ok: false, error: '缺少 paths 字段' });
906
+ return;
907
+ }
908
+ try {
909
+ const result = await gitIntegration.add(paths);
910
+ res.json({ ok: result.success, message: result.message });
911
+ }
912
+ catch (e) {
913
+ res.status(500).json({ ok: false, error: e.message });
914
+ }
915
+ });
916
+ // 取消暂存
917
+ app.post('/api/git/reset', async (req, res) => {
918
+ const body = (req.body ?? {});
919
+ const paths = Array.isArray(body?.paths) ? body.paths.filter((p) => typeof p === 'string') : [];
920
+ if (!paths.length) {
921
+ res.status(400).json({ ok: false, error: '缺少 paths 字段' });
922
+ return;
923
+ }
924
+ try {
925
+ const result = await gitIntegration.reset(paths);
926
+ res.json({ ok: result.success, message: result.message });
927
+ }
928
+ catch (e) {
929
+ res.status(500).json({ ok: false, error: e.message });
930
+ }
931
+ });
932
+ // 提交
933
+ app.post('/api/git/commit', async (req, res) => {
934
+ const body = (req.body ?? {});
935
+ const message = typeof body?.message === 'string' ? body.message.trim() : '';
936
+ const paths = Array.isArray(body?.paths) ? body.paths.filter((p) => typeof p === 'string') : undefined;
937
+ if (!message) {
938
+ res.status(400).json({ ok: false, error: '提交信息不能为空' });
939
+ return;
940
+ }
941
+ try {
942
+ const result = await gitIntegration.commit(message, paths);
943
+ res.json({ ok: result.success, hash: result.hash, message: result.message });
944
+ }
945
+ catch (e) {
946
+ res.status(500).json({ ok: false, error: e.message });
947
+ }
948
+ });
949
+ // 获取分支列表
950
+ app.get('/api/git/branches', async (_req, res) => {
951
+ try {
952
+ const branches = await gitIntegration.branches();
953
+ res.json({ ok: true, branches });
954
+ }
955
+ catch (e) {
956
+ res.status(500).json({ ok: false, error: e.message });
957
+ }
958
+ });
959
+ // 切换分支
960
+ app.post('/api/git/checkout', async (req, res) => {
961
+ const body = (req.body ?? {});
962
+ const branch = typeof body?.branch === 'string' ? body.branch.trim() : '';
963
+ const createNew = body?.createNew === true;
964
+ if (!branch) {
965
+ res.status(400).json({ ok: false, error: '缺少 branch 字段' });
966
+ return;
967
+ }
968
+ try {
969
+ const result = await gitIntegration.checkout(branch, createNew);
970
+ res.json({ ok: result.success, message: result.message });
971
+ }
972
+ catch (e) {
973
+ res.status(500).json({ ok: false, error: e.message });
974
+ }
975
+ });
976
+ // 获取提交历史
977
+ app.get('/api/git/log', async (req, res) => {
978
+ const count = typeof req.query?.count === 'string' ? parseInt(req.query.count) : 20;
979
+ try {
980
+ const commits = await gitIntegration.log(Math.min(Math.max(count, 1), 100));
981
+ res.json({ ok: true, commits });
982
+ }
983
+ catch (e) {
984
+ res.status(500).json({ ok: false, error: e.message });
985
+ }
986
+ });
987
+ // 推送
988
+ app.post('/api/git/push', async (req, res) => {
989
+ const body = (req.body ?? {});
990
+ const remote = typeof body?.remote === 'string' ? body.remote : 'origin';
991
+ const branch = typeof body?.branch === 'string' ? body.branch : undefined;
992
+ const force = body?.force === true;
993
+ try {
994
+ const result = await gitIntegration.push(remote, branch, force);
995
+ res.json({ ok: result.success, message: result.message });
996
+ }
997
+ catch (e) {
998
+ res.status(500).json({ ok: false, error: e.message });
999
+ }
1000
+ });
1001
+ // 拉取
1002
+ app.post('/api/git/pull', async (req, res) => {
1003
+ const body = (req.body ?? {});
1004
+ const remote = typeof body?.remote === 'string' ? body.remote : 'origin';
1005
+ const branch = typeof body?.branch === 'string' ? body.branch : undefined;
1006
+ try {
1007
+ const result = await gitIntegration.pull(remote, branch);
1008
+ res.json({ ok: result.success, message: result.message });
1009
+ }
1010
+ catch (e) {
1011
+ res.status(500).json({ ok: false, error: e.message });
1012
+ }
1013
+ });
1014
+ // 初始化仓库
1015
+ app.post('/api/git/init', async (_req, res) => {
1016
+ try {
1017
+ const result = await gitIntegration.init();
1018
+ res.json({ ok: result.success, message: result.message });
1019
+ }
1020
+ catch (e) {
1021
+ res.status(500).json({ ok: false, error: e.message });
1022
+ }
1023
+ });
1024
+ /* ========== P2-3: 团队协作 API ========== */
1025
+ app.get('/api/team', (_req, res) => {
1026
+ try {
1027
+ const team = teamManager.getTeam();
1028
+ const stats = teamManager.getStats();
1029
+ res.json({ ok: true, team, stats });
1030
+ }
1031
+ catch (e) {
1032
+ res.status(500).json({ ok: false, error: e.message });
1033
+ }
1034
+ });
1035
+ app.put('/api/team/config', (req, res) => {
1036
+ try {
1037
+ const config = teamManager.updateConfig(req.body || {});
1038
+ res.json({ ok: true, config });
1039
+ }
1040
+ catch (e) {
1041
+ res.status(500).json({ ok: false, error: e.message });
1042
+ }
1043
+ });
1044
+ app.get('/api/team/members', (_req, res) => {
1045
+ try {
1046
+ res.json({ ok: true, members: teamManager.getMembers() });
1047
+ }
1048
+ catch (e) {
1049
+ res.status(500).json({ ok: false, error: e.message });
1050
+ }
1051
+ });
1052
+ app.post('/api/team/members/invite', (req, res) => {
1053
+ const body = (req.body ?? {});
1054
+ const name = typeof body?.name === 'string' ? body.name.trim() : '';
1055
+ const email = typeof body?.email === 'string' ? body.email.trim() : '';
1056
+ const role = body?.role || 'developer';
1057
+ if (!name || !email) {
1058
+ res.status(400).json({ ok: false, error: '缺少 name 或 email' });
1059
+ return;
1060
+ }
1061
+ try {
1062
+ const member = teamManager.inviteMember(name, email, role);
1063
+ res.json({ ok: true, member });
1064
+ }
1065
+ catch (e) {
1066
+ res.status(400).json({ ok: false, error: e.message });
1067
+ }
1068
+ });
1069
+ app.put('/api/team/members/:id/role', (req, res) => {
1070
+ try {
1071
+ const role = (req.body ?? {}).role;
1072
+ const member = teamManager.updateMemberRole(req.params.id, role);
1073
+ res.json({ ok: true, member });
1074
+ }
1075
+ catch (e) {
1076
+ res.status(400).json({ ok: false, error: e.message });
1077
+ }
1078
+ });
1079
+ app.delete('/api/team/members/:id', (req, res) => {
1080
+ try {
1081
+ teamManager.removeMember(req.params.id);
1082
+ res.json({ ok: true });
1083
+ }
1084
+ catch (e) {
1085
+ res.status(400).json({ ok: false, error: e.message });
1086
+ }
1087
+ });
1088
+ app.get('/api/team/tasks', (req, res) => {
1089
+ try {
1090
+ const status = typeof req.query?.status === 'string' ? req.query.status : undefined;
1091
+ res.json({ ok: true, tasks: teamManager.getTasks(status) });
1092
+ }
1093
+ catch (e) {
1094
+ res.status(500).json({ ok: false, error: e.message });
1095
+ }
1096
+ });
1097
+ app.post('/api/team/tasks', (req, res) => {
1098
+ const body = (req.body ?? {});
1099
+ const title = typeof body?.title === 'string' ? body.title.trim() : '';
1100
+ if (!title) {
1101
+ res.status(400).json({ ok: false, error: '缺少 title' });
1102
+ return;
1103
+ }
1104
+ try {
1105
+ const task = teamManager.createTask(title, body.createdBy || 'system', body);
1106
+ res.json({ ok: true, task });
1107
+ }
1108
+ catch (e) {
1109
+ res.status(500).json({ ok: false, error: e.message });
1110
+ }
1111
+ });
1112
+ app.put('/api/team/tasks/:id', (req, res) => {
1113
+ try {
1114
+ res.json({ ok: true, task: teamManager.updateTask(req.params.id, req.body || {}) });
1115
+ }
1116
+ catch (e) {
1117
+ res.status(400).json({ ok: false, error: e.message });
1118
+ }
1119
+ });
1120
+ app.delete('/api/team/tasks/:id', (req, res) => {
1121
+ try {
1122
+ teamManager.deleteTask(req.params.id);
1123
+ res.json({ ok: true });
1124
+ }
1125
+ catch (e) {
1126
+ res.status(400).json({ ok: false, error: e.message });
1127
+ }
1128
+ });
1129
+ app.post('/api/team/tasks/:id/comments', (req, res) => {
1130
+ const body = (req.body ?? {});
1131
+ const content = typeof body?.content === 'string' ? body.content.trim() : '';
1132
+ if (!content) {
1133
+ res.status(400).json({ ok: false, error: '缺少 content' });
1134
+ return;
1135
+ }
1136
+ try {
1137
+ const task = teamManager.addComment(req.params.id, body.author || 'anonymous', content);
1138
+ res.json({ ok: true, task });
1139
+ }
1140
+ catch (e) {
1141
+ res.status(400).json({ ok: false, error: e.message });
1142
+ }
1143
+ });
1144
+ app.get('/api/team/stats', (_req, res) => {
1145
+ try {
1146
+ res.json({ ok: true, stats: teamManager.getStats() });
1147
+ }
1148
+ catch (e) {
1149
+ res.status(500).json({ ok: false, error: e.message });
1150
+ }
1151
+ });
1152
+ /* ========== P3-2: SOLO 全自主编程 API ========== */
1153
+ // 启动 SOLO 任务
1154
+ app.post('/api/solo/run', async (req, res) => {
1155
+ const body = (req.body ?? {});
1156
+ const goal = typeof body?.goal === 'string' ? body.goal.trim() : '';
1157
+ if (!goal) {
1158
+ res.status(400).json({ ok: false, error: '缺少 goal' });
1159
+ return;
1160
+ }
1161
+ const taskId = `solo-${Date.now()}`;
1162
+ const config = {
1163
+ cwd: serverWorkspaceDir,
1164
+ goal,
1165
+ maxTasks: body.maxTasks || 8,
1166
+ maxRetries: body.maxRetries || 3,
1167
+ parallel: body.parallel || false,
1168
+ maxParallel: body.maxParallel || 2,
1169
+ planOnly: body.planOnly || false,
1170
+ verifyOnly: body.verifyOnly || false,
1171
+ autoCorrect: body.autoCorrect !== false,
1172
+ };
1173
+ // 创建子任务执行器(简化版,实际应委托给 orchestrator)
1174
+ const executor = async (focusedGoal) => {
1175
+ try {
1176
+ // 简化执行:返回成功,实际应调用 orchestrator
1177
+ return { ok: true, output: `已执行: ${focusedGoal}`, touchedFiles: [], iterations: 1 };
1178
+ }
1179
+ catch (e) {
1180
+ return { ok: false, output: e.message, touchedFiles: [], iterations: 0 };
1181
+ }
1182
+ };
1183
+ const agent = new solo_agent_1.SoloAgent(config, executor);
1184
+ soloTasks.set(taskId, { agent, status: 'running' });
1185
+ // 异步执行
1186
+ (async () => {
1187
+ try {
1188
+ const report = await agent.run();
1189
+ const task = soloTasks.get(taskId);
1190
+ if (task) {
1191
+ task.report = report;
1192
+ task.status = report.overall === 'failed' ? 'failed' : 'completed';
1193
+ }
1194
+ }
1195
+ catch (e) {
1196
+ const task = soloTasks.get(taskId);
1197
+ if (task) {
1198
+ task.status = 'failed';
1199
+ task.report = {
1200
+ goal, cwd: serverWorkspaceDir, overall: 'failed', phase: 'failed',
1201
+ startedAt: new Date().toISOString(), completedAt: new Date().toISOString(),
1202
+ totalTasks: 0, completedTasks: 0, failedTasks: 0, skippedTasks: 0,
1203
+ tasks: [], summary: `执行失败: ${e.message}`, events: [],
1204
+ };
1205
+ }
1206
+ }
1207
+ })();
1208
+ res.json({ ok: true, taskId, message: 'SOLO 任务已启动' });
1209
+ });
1210
+ // 获取 SOLO 任务状态
1211
+ app.get('/api/solo/status/:id', (req, res) => {
1212
+ const task = soloTasks.get(req.params.id);
1213
+ if (!task) {
1214
+ res.status(404).json({ ok: false, error: '任务不存在' });
1215
+ return;
1216
+ }
1217
+ const status = task.agent.getStatus();
1218
+ res.json({ ok: true, taskId: req.params.id, status: task.status, ...status });
1219
+ });
1220
+ // 获取 SOLO 任务报告
1221
+ app.get('/api/solo/report/:id', (req, res) => {
1222
+ const task = soloTasks.get(req.params.id);
1223
+ if (!task) {
1224
+ res.status(404).json({ ok: false, error: '任务不存在' });
1225
+ return;
1226
+ }
1227
+ if (!task.report) {
1228
+ res.status(202).json({ ok: false, error: '任务尚未完成', status: task.status });
1229
+ return;
1230
+ }
1231
+ res.json({ ok: true, report: task.report });
1232
+ });
1233
+ // 取消 SOLO 任务
1234
+ app.post('/api/solo/cancel/:id', (req, res) => {
1235
+ const task = soloTasks.get(req.params.id);
1236
+ if (!task) {
1237
+ res.status(404).json({ ok: false, error: '任务不存在' });
1238
+ return;
1239
+ }
1240
+ if (task.status === 'running') {
1241
+ task.status = 'failed';
1242
+ res.json({ ok: true, message: '任务已取消' });
1243
+ }
1244
+ else {
1245
+ res.json({ ok: false, error: '任务已结束,无法取消' });
1246
+ }
1247
+ });
1248
+ // 列出所有 SOLO 任务
1249
+ app.get('/api/solo/list', (_req, res) => {
1250
+ const tasks = Array.from(soloTasks.entries()).map(([id, task]) => ({
1251
+ id,
1252
+ status: task.status,
1253
+ goal: task.report?.goal || '',
1254
+ startedAt: task.report?.startedAt,
1255
+ completedAt: task.report?.completedAt,
1256
+ }));
1257
+ res.json({ ok: true, tasks });
1258
+ });
1259
+ /* ========== 阶段二-1:多智能体协同 API ========== */
1260
+ // 运行多智能体协同(架构师/开发/测试/评审)
1261
+ app.post('/api/multi-agent/run', async (req, res) => {
1262
+ const body = (req.body ?? {});
1263
+ const goal = typeof body?.goal === 'string' ? body.goal.trim() : '';
1264
+ if (!goal) {
1265
+ res.status(400).json({ ok: false, error: '缺少 goal' });
1266
+ return;
1267
+ }
1268
+ if (!sharedModelRouter) {
1269
+ res.status(500).json({ ok: false, error: '模型未配置' });
1270
+ return;
1271
+ }
1272
+ const config = {
1273
+ roles: body.roles || ['architect', 'developer', 'tester', 'reviewer'],
1274
+ maxRounds: body.maxRounds || 3,
1275
+ enableReviewLoop: body.enableReviewLoop !== false,
1276
+ };
1277
+ try {
1278
+ const result = await (0, multi_agent_1.runMultiAgent)(sharedModelRouter, goal, config, body.context);
1279
+ res.json({ ok: true, ...result });
1280
+ }
1281
+ catch (e) {
1282
+ res.status(500).json({ ok: false, error: '多智能体协同失败: ' + e.message });
1283
+ }
1284
+ });
1285
+ // 获取角色配置
1286
+ app.get('/api/multi-agent/roles', (_req, res) => {
1287
+ try {
1288
+ Promise.resolve().then(() => __importStar(require('../agent/multi-agent'))).then(({ AGENT_ROLES }) => {
1289
+ const roles = Object.values(AGENT_ROLES).map((r) => ({
1290
+ role: r.role,
1291
+ name: r.name,
1292
+ description: r.description,
1293
+ capabilities: r.capabilities,
1294
+ }));
1295
+ res.json({ ok: true, roles });
1296
+ });
1297
+ }
1298
+ catch (e) {
1299
+ res.status(500).json({ ok: false, error: e.message });
1300
+ }
1301
+ });
1302
+ /* ========== 阶段二-2:事件驱动 Agent API ========== */
1303
+ // 获取事件驱动配置
1304
+ app.get('/api/event-driven/config', (_req, res) => {
1305
+ try {
1306
+ res.json({ ok: true, config: eventDrivenManager.getConfig() });
1307
+ }
1308
+ catch (e) {
1309
+ res.status(500).json({ ok: false, error: e.message });
1310
+ }
1311
+ });
1312
+ // 获取事件历史
1313
+ app.get('/api/event-driven/events', (req, res) => {
1314
+ try {
1315
+ const limit = typeof req.query?.limit === 'string' ? parseInt(req.query.limit, 10) : 50;
1316
+ res.json({ ok: true, events: eventDrivenManager.getEvents(limit) });
1317
+ }
1318
+ catch (e) {
1319
+ res.status(500).json({ ok: false, error: e.message });
1320
+ }
1321
+ });
1322
+ // 手动触发事件
1323
+ app.post('/api/event-driven/trigger', (req, res) => {
1324
+ const body = (req.body ?? {});
1325
+ if (!body.type || !body.title) {
1326
+ res.status(400).json({ ok: false, error: '缺少 type 或 title' });
1327
+ return;
1328
+ }
1329
+ eventDrivenManager.triggerEvent({
1330
+ type: body.type,
1331
+ severity: body.severity || 'info',
1332
+ title: body.title,
1333
+ description: body.description || '',
1334
+ source: body.source || 'manual',
1335
+ payload: body.payload,
1336
+ }).then((event) => res.json({ ok: true, event }))
1337
+ .catch((e) => res.status(500).json({ ok: false, error: e.message }));
1338
+ });
1339
+ // Cron 任务 CRUD
1340
+ app.post('/api/event-driven/cron', (req, res) => {
1341
+ const body = (req.body ?? {});
1342
+ if (!body.name || !body.cron || !body.task) {
1343
+ res.status(400).json({ ok: false, error: '缺少 name/cron/task' });
1344
+ return;
1345
+ }
1346
+ try {
1347
+ const task = eventDrivenManager.addCronTask({
1348
+ name: body.name,
1349
+ cron: body.cron,
1350
+ task: body.task,
1351
+ enabled: body.enabled !== false,
1352
+ debounceMs: body.debounceMs || 0,
1353
+ });
1354
+ res.json({ ok: true, task });
1355
+ }
1356
+ catch (e) {
1357
+ res.status(500).json({ ok: false, error: e.message });
1358
+ }
1359
+ });
1360
+ app.put('/api/event-driven/cron/:id', (req, res) => {
1361
+ try {
1362
+ const task = eventDrivenManager.updateCronTask(req.params.id, (req.body ?? {}));
1363
+ if (!task) {
1364
+ res.status(404).json({ ok: false, error: '任务不存在' });
1365
+ return;
1366
+ }
1367
+ res.json({ ok: true, task });
1368
+ }
1369
+ catch (e) {
1370
+ res.status(500).json({ ok: false, error: e.message });
1371
+ }
1372
+ });
1373
+ app.delete('/api/event-driven/cron/:id', (req, res) => {
1374
+ try {
1375
+ const ok = eventDrivenManager.removeCronTask(req.params.id);
1376
+ res.json({ ok, message: ok ? '已删除' : '任务不存在' });
1377
+ }
1378
+ catch (e) {
1379
+ res.status(500).json({ ok: false, error: e.message });
1380
+ }
1381
+ });
1382
+ // Webhook 接收
1383
+ app.post('/api/event-driven/webhook/:path', (req, res) => {
1384
+ const path = `/webhook/${req.params.path}`;
1385
+ eventDrivenManager.handleWebhook(path, (req.body || {}), req.headers)
1386
+ .then((result) => res.json(result))
1387
+ .catch((e) => res.status(500).json({ ok: false, error: e.message }));
1388
+ });
1389
+ /* ========== 阶段二-3:自定义 Agent API ========== */
1390
+ // 获取所有自定义 Agent
1391
+ app.get('/api/custom-agents', (req, res) => {
1392
+ try {
1393
+ const category = typeof req.query?.category === 'string' ? req.query.category : undefined;
1394
+ res.json({ ok: true, agents: customAgentManager.getAllAgents(category) });
1395
+ }
1396
+ catch (e) {
1397
+ res.status(500).json({ ok: false, error: e.message });
1398
+ }
1399
+ });
1400
+ // 获取 Agent 分类
1401
+ app.get('/api/custom-agents/categories', (_req, res) => {
1402
+ try {
1403
+ res.json({ ok: true, categories: customAgentManager.getCategories() });
1404
+ }
1405
+ catch (e) {
1406
+ res.status(500).json({ ok: false, error: e.message });
1407
+ }
1408
+ });
1409
+ // 匹配推荐 Agent
1410
+ app.post('/api/custom-agents/match', (req, res) => {
1411
+ const body = (req.body ?? {});
1412
+ const input = typeof body?.input === 'string' ? body.input : '';
1413
+ if (!input) {
1414
+ res.status(400).json({ ok: false, error: '缺少 input' });
1415
+ return;
1416
+ }
1417
+ try {
1418
+ const limit = typeof body?.limit === 'number' ? body.limit : 3;
1419
+ res.json({ ok: true, agents: customAgentManager.matchAgents(input, limit) });
1420
+ }
1421
+ catch (e) {
1422
+ res.status(500).json({ ok: false, error: e.message });
1423
+ }
1424
+ });
1425
+ // 获取单个 Agent
1426
+ app.get('/api/custom-agents/:id', (req, res) => {
1427
+ try {
1428
+ const agent = customAgentManager.getAgent(req.params.id);
1429
+ if (!agent) {
1430
+ res.status(404).json({ ok: false, error: 'Agent 不存在' });
1431
+ return;
1432
+ }
1433
+ res.json({ ok: true, agent });
1434
+ }
1435
+ catch (e) {
1436
+ res.status(500).json({ ok: false, error: e.message });
1437
+ }
1438
+ });
1439
+ // 创建自定义 Agent
1440
+ app.post('/api/custom-agents', (req, res) => {
1441
+ const body = (req.body ?? {});
1442
+ if (!body.name || !body.systemPrompt) {
1443
+ res.status(400).json({ ok: false, error: '缺少 name 或 systemPrompt' });
1444
+ return;
1445
+ }
1446
+ try {
1447
+ const agent = customAgentManager.createAgent({
1448
+ name: body.name,
1449
+ description: body.description || '',
1450
+ systemPrompt: body.systemPrompt,
1451
+ tools: body.tools || [],
1452
+ modelConfig: body.modelConfig || { temperature: 0.3, maxTokens: 2000, timeoutMs: 30000 },
1453
+ triggers: body.triggers || [],
1454
+ icon: body.icon || '🤖',
1455
+ category: body.category || 'custom',
1456
+ enabled: body.enabled !== false,
1457
+ author: body.author,
1458
+ version: body.version || '1.0.0',
1459
+ });
1460
+ res.json({ ok: true, agent });
1461
+ }
1462
+ catch (e) {
1463
+ res.status(500).json({ ok: false, error: e.message });
1464
+ }
1465
+ });
1466
+ // 更新自定义 Agent
1467
+ app.put('/api/custom-agents/:id', (req, res) => {
1468
+ try {
1469
+ const agent = customAgentManager.updateAgent(req.params.id, (req.body ?? {}));
1470
+ if (!agent) {
1471
+ res.status(404).json({ ok: false, error: 'Agent 不存在' });
1472
+ return;
1473
+ }
1474
+ res.json({ ok: true, agent });
1475
+ }
1476
+ catch (e) {
1477
+ res.status(500).json({ ok: false, error: e.message });
1478
+ }
1479
+ });
1480
+ // 删除自定义 Agent
1481
+ app.delete('/api/custom-agents/:id', (req, res) => {
1482
+ try {
1483
+ const ok = customAgentManager.deleteAgent(req.params.id);
1484
+ res.json({ ok, message: ok ? '已删除' : 'Agent 不存在或为内置 Agent' });
1485
+ }
1486
+ catch (e) {
1487
+ res.status(500).json({ ok: false, error: e.message });
1488
+ }
1489
+ });
1490
+ // 执行自定义 Agent
1491
+ app.post('/api/custom-agents/:id/execute', async (req, res) => {
1492
+ const body = (req.body ?? {});
1493
+ const input = typeof body?.input === 'string' ? body.input : '';
1494
+ if (!input) {
1495
+ res.status(400).json({ ok: false, error: '缺少 input' });
1496
+ return;
1497
+ }
1498
+ if (!sharedModelRouter) {
1499
+ res.status(500).json({ ok: false, error: '模型未配置' });
1500
+ return;
1501
+ }
1502
+ try {
1503
+ const result = await customAgentManager.executeAgent(req.params.id, input, sharedModelRouter, body?.context);
1504
+ res.json({ ok: result.success, ...result });
1505
+ }
1506
+ catch (e) {
1507
+ res.status(500).json({ ok: false, error: e.message });
1508
+ }
1509
+ });
1510
+ /* ========== 打开本地文件夹/浏览器 ========== */
1511
+ app.post('/api/open/folder', (req, res) => {
1512
+ const body = (req.body ?? {});
1513
+ const dir = typeof body?.path === 'string' ? body.path.trim() : serverWorkspaceDir;
1514
+ if (!dir || !assertPathAllowed(dir, res))
1515
+ return;
1516
+ try {
1517
+ openFolder(dir);
1518
+ res.json({ ok: true });
1519
+ }
1520
+ catch (e) {
1521
+ res.status(500).json({ ok: false, error: '打开失败: ' + e.message });
1522
+ }
1523
+ });
1524
+ app.post('/api/open/browser', (req, res) => {
1525
+ const body = (req.body ?? {});
1526
+ const url = typeof body?.url === 'string' ? body.url.trim() : '';
1527
+ if (!url) {
1528
+ res.status(400).json({ ok: false, error: '缺少 url 字段' });
1529
+ return;
1530
+ }
1531
+ try {
1532
+ openBrowser(url);
1533
+ res.json({ ok: true });
1534
+ }
1535
+ catch (e) {
1536
+ res.status(500).json({ ok: false, error: '打开失败: ' + e.message });
1537
+ }
1538
+ });
1539
+ /* ========== 上传文件/图片 ========== */
1540
+ const uploadsDir = () => (0, path_1.join)(homeDir, 'uploads');
1541
+ app.post('/api/upload', (req, res) => {
1542
+ const body = (req.body ?? {});
1543
+ const name = typeof body?.name === 'string' ? body.name.trim() : '';
1544
+ const mime = typeof body?.mime === 'string' ? body.mime.trim() : 'application/octet-stream';
1545
+ const data = typeof body?.dataBase64 === 'string' ? body.dataBase64.trim() : '';
1546
+ if (!name || !data) {
1547
+ res.status(400).json({ ok: false, error: '缺少 name 或 dataBase64 字段' });
1548
+ return;
1549
+ }
1550
+ try {
1551
+ (0, fs_1.mkdirSync)(uploadsDir(), { recursive: true });
1552
+ const safeName = name.replace(/[^a-zA-Z0-9_.\-]/g, '_');
1553
+ const dest = (0, path_1.join)(uploadsDir(), `${Date.now()}_${safeName}`);
1554
+ (0, fs_1.writeFileSync)(dest, Buffer.from(data, 'base64'));
1555
+ res.json({ ok: true, path: dest, name, mime });
1556
+ }
1557
+ catch (e) {
1558
+ res.status(500).json({ ok: false, error: '上传失败: ' + e.message });
1559
+ }
1560
+ });
1561
+ /* ========== 系统截图(调用 Windows 截图工具,不弹浏览器分享框) ========== */
1562
+ app.post('/api/screenshot', (_req, res) => {
1563
+ try {
1564
+ // Windows 10/11 内置截图工具(和 Win+Shift+S 效果一样)
1565
+ // 调用后直接进入截图模式,用户截图后图片保存到剪贴板
1566
+ if (process.platform === 'win32') {
1567
+ (0, child_process_1.exec)('explorer.exe ms-screenclip:', (err) => {
1568
+ if (err) {
1569
+ res.status(500).json({ ok: false, error: '启动截图工具失败: ' + err.message });
1570
+ }
1571
+ else {
1572
+ res.json({ ok: true, message: '截图工具已启动,截图后按 Ctrl+V 粘贴到输入框' });
1573
+ }
1574
+ });
1575
+ }
1576
+ else {
1577
+ res.status(400).json({ ok: false, error: '仅支持 Windows 系统' });
1578
+ }
1579
+ }
1580
+ catch (e) {
1581
+ res.status(500).json({ ok: false, error: '启动截图工具失败: ' + e.message });
1582
+ }
1583
+ });
1584
+ /* ========== 电脑操作(鼠标/键盘/截图,用语言控制电脑) ========== */
1585
+ // 执行 PowerShell 命令的辅助函数
1586
+ const runPowerShell = (script) => {
1587
+ return new Promise((resolve, reject) => {
1588
+ const ps = (0, child_process_1.spawn)('powershell.exe', ['-NoProfile', '-Command', script], {
1589
+ windowsHide: true,
1590
+ stdio: ['ignore', 'pipe', 'pipe'],
1591
+ });
1592
+ let stdout = '';
1593
+ let stderr = '';
1594
+ ps.stdout.on('data', (d) => { stdout += d.toString(); });
1595
+ ps.stderr.on('data', (d) => { stderr += d.toString(); });
1596
+ ps.on('close', (code) => {
1597
+ if (code === 0)
1598
+ resolve(stdout.trim());
1599
+ else
1600
+ reject(new Error(stderr || `PowerShell exited with code ${code}`));
1601
+ });
1602
+ ps.on('error', reject);
1603
+ });
1604
+ };
1605
+ // 截图
1606
+ app.post('/api/computer/screenshot', async (_req, res) => {
1607
+ try {
1608
+ if (process.platform !== 'win32') {
1609
+ res.status(400).json({ ok: false, error: '仅支持 Windows 系统' });
1610
+ return;
1611
+ }
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)
1624
+ `;
1625
+ const base64 = await runPowerShell(script);
1626
+ res.json({ ok: true, image: 'data:image/png;base64,' + base64, width: 1920, height: 1080 });
1627
+ }
1628
+ catch (e) {
1629
+ res.status(500).json({ ok: false, error: '截图失败: ' + e.message });
1630
+ }
1631
+ });
1632
+ // 移动鼠标
1633
+ app.post('/api/computer/mouse/move', async (req, res) => {
1634
+ try {
1635
+ const body = (req.body ?? {});
1636
+ const x = parseInt(body?.x ?? '0');
1637
+ const y = parseInt(body?.y ?? '0');
1638
+ if (isNaN(x) || isNaN(y)) {
1639
+ res.status(400).json({ ok: false, error: '缺少 x 或 y 坐标' });
1640
+ return;
1641
+ }
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"
1653
+ `;
1654
+ await runPowerShell(script);
1655
+ res.json({ ok: true, x, y });
1656
+ }
1657
+ catch (e) {
1658
+ res.status(500).json({ ok: false, error: '移动鼠标失败: ' + e.message });
1659
+ }
1660
+ });
1661
+ // 点击鼠标
1662
+ app.post('/api/computer/mouse/click', async (req, res) => {
1663
+ try {
1664
+ const body = (req.body ?? {});
1665
+ const button = (body?.button ?? 'left');
1666
+ const x = body?.x !== undefined ? parseInt(body.x) : null;
1667
+ const y = body?.y !== undefined ? parseInt(body.y) : null;
1668
+ const doubleClick = body?.double === true;
1669
+ let clickFlag = '0x0002'; // left down
1670
+ let upFlag = '0x0004'; // left up
1671
+ if (button === 'right') {
1672
+ clickFlag = '0x0008';
1673
+ upFlag = '0x0010';
1674
+ }
1675
+ const movePart = (x !== null && y !== null) ? `[MouseHelper]::SetCursorPos(${x}, ${y}) | Out-Null; Start-Sleep -Milliseconds 100;` : '';
1676
+ const clickPart = doubleClick
1677
+ ? `[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
+ : `[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"
1693
+ `;
1694
+ await runPowerShell(script);
1695
+ res.json({ ok: true, button, x, y, double: doubleClick });
1696
+ }
1697
+ catch (e) {
1698
+ res.status(500).json({ ok: false, error: '点击鼠标失败: ' + e.message });
1699
+ }
1700
+ });
1701
+ // 输入文字
1702
+ app.post('/api/computer/keyboard/type', async (req, res) => {
1703
+ try {
1704
+ const body = (req.body ?? {});
1705
+ const text = (body?.text ?? '');
1706
+ if (!text) {
1707
+ res.status(400).json({ ok: false, error: '缺少 text 字段' });
1708
+ return;
1709
+ }
1710
+ // SendKeys 需要转义特殊字符
1711
+ 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"
1716
+ `;
1717
+ await runPowerShell(script);
1718
+ res.json({ ok: true, text });
1719
+ }
1720
+ catch (e) {
1721
+ res.status(500).json({ ok: false, error: '输入文字失败: ' + e.message });
1722
+ }
1723
+ });
1724
+ // 按键(快捷键)
1725
+ app.post('/api/computer/keyboard/press', async (req, res) => {
1726
+ try {
1727
+ const body = (req.body ?? {});
1728
+ const key = (body?.key ?? '');
1729
+ if (!key) {
1730
+ res.status(400).json({ ok: false, error: '缺少 key 字段' });
1731
+ return;
1732
+ }
1733
+ const script = `
1734
+ Add-Type -AssemblyName System.Windows.Forms
1735
+ [System.Windows.Forms.SendKeys]::SendWait('${key.replace(/'/g, "''")}')
1736
+ Write-Output "ok"
1737
+ `;
1738
+ await runPowerShell(script);
1739
+ res.json({ ok: true, key });
1740
+ }
1741
+ catch (e) {
1742
+ res.status(500).json({ ok: false, error: '按键失败: ' + e.message });
1743
+ }
1744
+ });
1745
+ const nodesFile = (0, path_1.join)(homeDir, 'nodes.json');
1746
+ const sourcesFile = (0, path_1.join)(homeDir, 'sources.json');
1747
+ const nodeDataDir = (0, path_1.join)(homeDir, 'node-data');
1748
+ function loadNodes() {
1749
+ const list = loadJsonFile(nodesFile, []);
1750
+ return Array.isArray(list) ? list : [];
1751
+ }
1752
+ function saveNodes(list) {
1753
+ return saveJsonFile(nodesFile, list);
1754
+ }
1755
+ function loadSources() {
1756
+ const list = loadJsonFile(sourcesFile, []);
1757
+ return Array.isArray(list) ? list : [];
1758
+ }
1759
+ function saveSources(list) {
1760
+ return saveJsonFile(sourcesFile, list);
1761
+ }
1762
+ function loadNodeData(nodeId, type, fallback) {
1763
+ const file = (0, path_1.join)(nodeDataDir, nodeId, `${type}.json`);
1764
+ return loadJsonFile(file, fallback);
1765
+ }
1766
+ function saveNodeData(nodeId, type, data) {
1767
+ const file = (0, path_1.join)(nodeDataDir, nodeId, `${type}.json`);
1768
+ return saveJsonFile(file, data);
1769
+ }
1770
+ // 测试节点连接
1771
+ async function testNodeConnection(node) {
1772
+ try {
1773
+ if (node.type === 'local') {
1774
+ const dir = (0, path_1.resolve)(node.url);
1775
+ if (!(0, fs_1.existsSync)(dir) || !(0, fs_1.statSync)(dir).isDirectory()) {
1776
+ return { ok: false, error: '本地目录不存在' };
1777
+ }
1778
+ const caps = [];
1779
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'templates.json')))
1780
+ caps.push('templates');
1781
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'skills.json')))
1782
+ caps.push('skills');
1783
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'office.json')))
1784
+ caps.push('office');
1785
+ return { ok: true, capabilities: caps.length ? caps : node.capabilities };
1786
+ }
1787
+ // http / git 类型:请求节点的 /api/health 或 manifest
1788
+ const url = node.type === 'http'
1789
+ ? node.url.replace(/\/$/, '') + '/api/health'
1790
+ : node.url;
1791
+ const headers = { 'User-Agent': 'fhcode-node/1.0' };
1792
+ if (node.apiKey)
1793
+ headers['Authorization'] = 'Bearer ' + node.apiKey;
1794
+ const r = await fetchWithRetry(url, 10000, 0);
1795
+ if (!r.ok)
1796
+ return { ok: false, error: `HTTP ${r.status}` };
1797
+ if (node.type === 'http') {
1798
+ const data = await r.json().catch(() => ({}));
1799
+ return { ok: true, capabilities: Array.isArray(data.capabilities) ? data.capabilities : node.capabilities };
1800
+ }
1801
+ return { ok: true, capabilities: node.capabilities };
1802
+ }
1803
+ catch (e) {
1804
+ return { ok: false, error: e?.message || '连接失败' };
1805
+ }
1806
+ }
1807
+ // 从节点同步数据
1808
+ async function syncNodeData(node) {
1809
+ const synced = [];
1810
+ try {
1811
+ if (node.type === 'local') {
1812
+ const dir = (0, path_1.resolve)(node.url);
1813
+ for (const cap of node.capabilities) {
1814
+ const file = (0, path_1.join)(dir, `${cap}.json`);
1815
+ if ((0, fs_1.existsSync)(file)) {
1816
+ const data = JSON.parse((0, fs_1.readFileSync)(file, 'utf8'));
1817
+ saveNodeData(node.id, cap, data);
1818
+ synced.push(cap);
1819
+ }
1820
+ }
1821
+ return { ok: true, synced };
1822
+ }
1823
+ // http 类型
1824
+ if (node.type === 'http') {
1825
+ const base = node.url.replace(/\/$/, '');
1826
+ const headers = { 'User-Agent': 'fhcode-node/1.0' };
1827
+ if (node.apiKey)
1828
+ headers['Authorization'] = 'Bearer ' + node.apiKey;
1829
+ for (const cap of node.capabilities) {
1830
+ try {
1831
+ const r = await fetch(`${base}/api/node/${cap}`, { headers, signal: AbortSignal.timeout(15000) });
1832
+ if (r.ok) {
1833
+ const data = await r.json();
1834
+ const list = Array.isArray(data) ? data : (data.items || data.list || []);
1835
+ saveNodeData(node.id, cap, list);
1836
+ synced.push(cap);
1837
+ }
1838
+ }
1839
+ catch { /* skip */ }
1840
+ }
1841
+ return { ok: true, synced };
1842
+ }
1843
+ // git 类型:暂不支持自动 clone,标记为需手动同步
1844
+ return { ok: false, error: 'Git 节点暂不支持自动同步,请使用本地目录方式', synced };
1845
+ }
1846
+ catch (e) {
1847
+ return { ok: false, error: e?.message || '同步失败', synced };
1848
+ }
1849
+ }
1850
+ // 节点 CRUD
1851
+ app.get('/api/nodes', (_req, res) => {
1852
+ const nodes = loadNodes().map((n) => {
1853
+ const { apiKey, ...rest } = n;
1854
+ return { ...rest, hasApiKey: !!apiKey };
1855
+ });
1856
+ res.json({ ok: true, nodes });
1857
+ });
1858
+ app.post('/api/nodes', async (req, res) => {
1859
+ const body = (req.body ?? {});
1860
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
1861
+ const type = ['http', 'git', 'local'].find((t) => t === body.type) || 'http';
1862
+ const url = typeof body.url === 'string' ? body.url.trim() : '';
1863
+ const apiKey = typeof body.apiKey === 'string' && body.apiKey.trim() ? body.apiKey.trim() : undefined;
1864
+ const capabilities = Array.isArray(body.capabilities) ? body.capabilities.filter((c) => ['templates', 'skills', 'office'].includes(c)) : ['templates', 'skills', 'office'];
1865
+ if (!name || !url) {
1866
+ res.status(400).json({ ok: false, error: '缺少 name 或 url 字段' });
1867
+ return;
1868
+ }
1869
+ const node = {
1870
+ id: `node_${Date.now()}_${(0, crypto_1.randomUUID)().slice(0, 4)}`,
1871
+ name, type, url, apiKey, capabilities,
1872
+ status: 'disconnected',
1873
+ enabled: true,
1874
+ createdAt: new Date().toISOString(),
1875
+ };
1876
+ const list = loadNodes();
1877
+ list.push(node);
1878
+ if (!saveNodes(list)) {
1879
+ res.status(500).json({ ok: false, error: '节点保存失败' });
1880
+ return;
1881
+ }
1882
+ // 自动测试连接
1883
+ const test = await testNodeConnection(node);
1884
+ node.status = test.ok ? 'connected' : 'error';
1885
+ node.lastError = test.error;
1886
+ if (test.capabilities)
1887
+ node.capabilities = test.capabilities;
1888
+ saveNodes(list);
1889
+ res.status(201).json({ ok: true, node: { ...node, apiKey: undefined, hasApiKey: !!apiKey }, connection: test });
1890
+ });
1891
+ app.put('/api/nodes/:id', async (req, res) => {
1892
+ const list = loadNodes();
1893
+ const idx = list.findIndex((n) => n.id === req.params.id);
1894
+ if (idx < 0) {
1895
+ res.status(404).json({ ok: false, error: '节点不存在' });
1896
+ return;
1897
+ }
1898
+ const body = (req.body ?? {});
1899
+ const node = list[idx];
1900
+ if (typeof body.name === 'string' && body.name.trim())
1901
+ node.name = body.name.trim();
1902
+ if (typeof body.url === 'string' && body.url.trim())
1903
+ node.url = body.url.trim();
1904
+ if (typeof body.type === 'string' && ['http', 'git', 'local'].includes(body.type))
1905
+ node.type = body.type;
1906
+ if (typeof body.apiKey === 'string')
1907
+ node.apiKey = body.apiKey.trim() ? body.apiKey.trim() : undefined;
1908
+ if (Array.isArray(body.capabilities))
1909
+ node.capabilities = body.capabilities.filter((c) => ['templates', 'skills', 'office'].includes(c));
1910
+ if (typeof body.enabled === 'boolean')
1911
+ node.enabled = body.enabled;
1912
+ if (!saveNodes(list)) {
1913
+ res.status(500).json({ ok: false, error: '节点更新失败' });
1914
+ return;
1915
+ }
1916
+ res.json({ ok: true, node: { ...node, apiKey: undefined, hasApiKey: !!node.apiKey } });
1917
+ });
1918
+ app.delete('/api/nodes/:id', (req, res) => {
1919
+ const list = loadNodes();
1920
+ const next = list.filter((n) => n.id !== req.params.id);
1921
+ if (next.length === list.length) {
1922
+ res.status(404).json({ ok: false, error: '节点不存在' });
1923
+ return;
1924
+ }
1925
+ if (!saveNodes(next)) {
1926
+ res.status(500).json({ ok: false, error: '节点删除失败' });
1927
+ return;
1928
+ }
1929
+ res.json({ ok: true });
1930
+ });
1931
+ app.post('/api/nodes/:id/test', async (req, res) => {
1932
+ const node = loadNodes().find((n) => n.id === req.params.id);
1933
+ if (!node) {
1934
+ res.status(404).json({ ok: false, error: '节点不存在' });
1935
+ return;
1936
+ }
1937
+ const test = await testNodeConnection(node);
1938
+ // 更新节点状态
1939
+ const list = loadNodes();
1940
+ const idx = list.findIndex((n) => n.id === node.id);
1941
+ if (idx >= 0) {
1942
+ list[idx].status = test.ok ? 'connected' : 'error';
1943
+ list[idx].lastError = test.error;
1944
+ if (test.capabilities)
1945
+ list[idx].capabilities = test.capabilities;
1946
+ saveNodes(list);
1947
+ }
1948
+ res.json({ ok: test.ok, error: test.error, capabilities: test.capabilities });
1949
+ });
1950
+ app.post('/api/nodes/:id/sync', async (req, res) => {
1951
+ const node = loadNodes().find((n) => n.id === req.params.id);
1952
+ if (!node) {
1953
+ res.status(404).json({ ok: false, error: '节点不存在' });
1954
+ return;
1955
+ }
1956
+ const result = await syncNodeData(node);
1957
+ const list = loadNodes();
1958
+ const idx = list.findIndex((n) => n.id === node.id);
1959
+ if (idx >= 0) {
1960
+ list[idx].lastSyncAt = new Date().toISOString();
1961
+ list[idx].status = result.ok ? 'connected' : 'error';
1962
+ list[idx].lastError = result.error;
1963
+ saveNodes(list);
1964
+ }
1965
+ res.json({ ok: result.ok, error: result.error, synced: result.synced });
1966
+ });
1967
+ // 自定义来源 CRUD
1968
+ app.get('/api/sources', (_req, res) => {
1969
+ res.json({ ok: true, sources: loadSources() });
1970
+ });
1971
+ app.post('/api/sources', (req, res) => {
1972
+ const body = (req.body ?? {});
1973
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
1974
+ const type = ['templates', 'skills', 'office'].find((t) => t === body.type);
1975
+ const url = typeof body.url === 'string' ? body.url.trim() : '';
1976
+ const nodeId = typeof body.nodeId === 'string' && body.nodeId.trim() ? body.nodeId.trim() : undefined;
1977
+ if (!name || !type || !url) {
1978
+ res.status(400).json({ ok: false, error: '缺少 name / type / url 字段' });
1979
+ return;
1980
+ }
1981
+ const source = {
1982
+ id: `src_${Date.now()}_${(0, crypto_1.randomUUID)().slice(0, 4)}`,
1983
+ name, type, url, nodeId,
1984
+ enabled: true,
1985
+ createdAt: new Date().toISOString(),
1986
+ };
1987
+ const list = loadSources();
1988
+ list.push(source);
1989
+ if (!saveSources(list)) {
1990
+ res.status(500).json({ ok: false, error: '来源保存失败' });
1991
+ return;
1992
+ }
1993
+ res.status(201).json({ ok: true, source });
1994
+ });
1995
+ app.put('/api/sources/:id', (req, res) => {
1996
+ const list = loadSources();
1997
+ const idx = list.findIndex((s) => s.id === req.params.id);
1998
+ if (idx < 0) {
1999
+ res.status(404).json({ ok: false, error: '来源不存在' });
2000
+ return;
2001
+ }
2002
+ const body = (req.body ?? {});
2003
+ const source = list[idx];
2004
+ if (typeof body.name === 'string' && body.name.trim())
2005
+ source.name = body.name.trim();
2006
+ if (typeof body.url === 'string' && body.url.trim())
2007
+ source.url = body.url.trim();
2008
+ if (typeof body.enabled === 'boolean')
2009
+ source.enabled = body.enabled;
2010
+ if (typeof body.nodeId === 'string')
2011
+ source.nodeId = body.nodeId.trim() || undefined;
2012
+ if (!saveSources(list)) {
2013
+ res.status(500).json({ ok: false, error: '来源更新失败' });
2014
+ return;
2015
+ }
2016
+ res.json({ ok: true, source });
2017
+ });
2018
+ app.delete('/api/sources/:id', (req, res) => {
2019
+ const list = loadSources();
2020
+ const next = list.filter((s) => s.id !== req.params.id);
2021
+ if (next.length === list.length) {
2022
+ res.status(404).json({ ok: false, error: '来源不存在' });
2023
+ return;
2024
+ }
2025
+ if (!saveSources(next)) {
2026
+ res.status(500).json({ ok: false, error: '来源删除失败' });
2027
+ return;
2028
+ }
2029
+ res.json({ ok: true });
2030
+ });
2031
+ // 从所有启用的节点收集数据
2032
+ function collectNodeTemplates() {
2033
+ const result = [];
2034
+ for (const node of loadNodes()) {
2035
+ if (!node.enabled || !node.capabilities.includes('templates'))
2036
+ continue;
2037
+ const data = loadNodeData(node.id, 'templates', []);
2038
+ if (Array.isArray(data)) {
2039
+ result.push(...data.map((t) => ({ ...t, source: node.name, nodeId: node.id })));
2040
+ }
2041
+ }
2042
+ return result;
2043
+ }
2044
+ function collectNodeSkills() {
2045
+ const result = [];
2046
+ for (const node of loadNodes()) {
2047
+ if (!node.enabled || !node.capabilities.includes('skills'))
2048
+ continue;
2049
+ const data = loadNodeData(node.id, 'skills', []);
2050
+ if (Array.isArray(data)) {
2051
+ result.push(...data.map((s) => ({ ...s, source: node.name, nodeId: node.id })));
2052
+ }
2053
+ }
2054
+ return result;
2055
+ }
2056
+ function collectNodeOffice() {
2057
+ const result = [];
2058
+ for (const node of loadNodes()) {
2059
+ if (!node.enabled || !node.capabilities.includes('office'))
2060
+ continue;
2061
+ const data = loadNodeData(node.id, 'office', []);
2062
+ if (Array.isArray(data)) {
2063
+ result.push(...data.map((c) => ({ ...c, source: node.name, nodeId: node.id })));
2064
+ }
2065
+ }
2066
+ return result;
2067
+ }
2068
+ /* ========== 技能市场(插件市场):聚合 ClawHub + Agent-Foundry + 自定义节点 + 自定义来源 ========== */
2069
+ const CLAWHUB_API = 'https://clawhub.ai/api/v1/skills';
2070
+ 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) {
2072
+ let lastErr;
2073
+ for (let attempt = 0; attempt <= retries; attempt++) {
2074
+ const ctrl = new AbortController();
2075
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
2076
+ try {
2077
+ const r = await fetch(url, {
2078
+ signal: ctrl.signal,
2079
+ headers: { 'User-Agent': 'fhcode-web/0.5.1' },
2080
+ });
2081
+ clearTimeout(timer);
2082
+ return r;
2083
+ }
2084
+ catch (e) {
2085
+ clearTimeout(timer);
2086
+ lastErr = e;
2087
+ if (attempt < retries)
2088
+ await new Promise((res) => setTimeout(res, 500));
2089
+ }
2090
+ }
2091
+ throw lastErr;
2092
+ }
2093
+ async function fetchClawHubSkills(keyword, limit) {
2094
+ try {
2095
+ const url = `${CLAWHUB_API}?limit=${limit}${keyword ? `&q=${encodeURIComponent(keyword)}` : ''}`;
2096
+ const r = await fetchWithRetry(url);
2097
+ if (!r.ok)
2098
+ return [];
2099
+ const data = (await r.json());
2100
+ return (data.items || []).map((it) => ({
2101
+ id: `clawhub:${String(it.slug || '')}`,
2102
+ name: String(it.displayName || it.slug || ''),
2103
+ description: String(it.summary || it.description || ''),
2104
+ source: 'clawhub',
2105
+ author: it.slug ? String(it.slug).split('/')[0] : undefined,
2106
+ category: 'market',
2107
+ tags: Array.isArray(it.topics) ? it.topics : [],
2108
+ downloads: Number(it.stats?.downloads ?? 0),
2109
+ installHint: `npx skills add ${String(it.slug || '')}`,
2110
+ homepage: `https://clawhub.ai/${String(it.slug || '')}`,
2111
+ rawUrl: `https://clawhub.ai/${String(it.slug || '')}`,
2112
+ }));
2113
+ }
2114
+ catch (e) {
2115
+ console.error('[clawhub] fetch failed:', e);
2116
+ return [];
2117
+ }
2118
+ }
2119
+ async function fetchAgentFoundrySkills(keyword, limit) {
2120
+ try {
2121
+ const r = await fetchWithRetry(AGENT_FOUNDRY_CATALOG);
2122
+ if (!r.ok)
2123
+ return [];
2124
+ const data = (await r.json());
2125
+ const arr = Array.isArray(data.skills) ? data.skills : [];
2126
+ let list = arr.map((it) => {
2127
+ const name = String(it.name || '');
2128
+ const publicPath = String(it.public_path || '');
2129
+ const category = String(it.category || 'market');
2130
+ const rawUrl = publicPath
2131
+ ? `https://raw.githubusercontent.com/hebertzhu/agent-foundry/main/${publicPath}`
2132
+ : `https://github.com/hebertzhu/agent-foundry`;
2133
+ return {
2134
+ id: `agent-foundry:${name}`,
2135
+ name,
2136
+ description: `Agent-Foundry 技能包 · 分类:${category}${publicPath ? ` · 源:${publicPath}` : ''}`,
2137
+ source: 'agent-foundry',
2138
+ author: 'hebertzhu',
2139
+ category,
2140
+ tags: Array.isArray(it.packs) ? it.packs : [category],
2141
+ downloads: 0,
2142
+ installHint: `npm i -g @agent-foundry/cli && agent-foundry install ${name}`,
2143
+ homepage: `https://github.com/hebertzhu/agent-foundry`,
2144
+ rawUrl,
2145
+ };
2146
+ });
2147
+ if (keyword) {
2148
+ const kw = keyword.toLowerCase();
2149
+ list = list.filter((s) => s.name.toLowerCase().includes(kw) ||
2150
+ s.description.toLowerCase().includes(kw) ||
2151
+ (s.tags || []).some((t) => t.toLowerCase().includes(kw)));
2152
+ }
2153
+ return list.slice(0, limit);
2154
+ }
2155
+ catch (e) {
2156
+ console.error('[agent-foundry] fetch failed:', e);
2157
+ return [];
2158
+ }
2159
+ }
2160
+ app.get('/api/skills/market', async (req, res) => {
2161
+ const q = typeof req.query.q === 'string' ? req.query.q.trim() : '';
2162
+ const source = typeof req.query.source === 'string' ? req.query.source : 'all';
2163
+ const limit = Math.min(Number(req.query.limit) || 50, 200);
2164
+ let results = [];
2165
+ if (source === 'clawhub' || source === 'all')
2166
+ results = results.concat(await fetchClawHubSkills(q, limit));
2167
+ if (source === 'agent-foundry' || source === 'all')
2168
+ results = results.concat(await fetchAgentFoundrySkills(q, limit));
2169
+ // 从节点同步的技能
2170
+ if (source === 'node' || source === 'all') {
2171
+ const nodeSkills = collectNodeSkills();
2172
+ results = results.concat(nodeSkills.map((s) => ({
2173
+ id: `node:${s.id}`,
2174
+ name: s.name,
2175
+ description: s.description,
2176
+ source: s.source,
2177
+ author: s.author,
2178
+ category: s.category,
2179
+ tags: s.tags,
2180
+ downloads: s.downloads,
2181
+ installHint: s.installHint,
2182
+ homepage: s.homepage,
2183
+ rawUrl: s.rawUrl,
2184
+ })));
2185
+ }
2186
+ // 从自定义来源拉取技能(HTTP URL 返回 JSON 数组)
2187
+ if (source === 'custom' || source === 'all') {
2188
+ for (const src of loadSources()) {
2189
+ if (!src.enabled || src.type !== 'skills')
2190
+ continue;
2191
+ try {
2192
+ const r = await fetchWithRetry(src.url, 10000, 0);
2193
+ if (r.ok) {
2194
+ const data = await r.json();
2195
+ const list = Array.isArray(data) ? data : (data.items || data.skills || []);
2196
+ results = results.concat(list.map((s) => ({
2197
+ id: `custom:${src.id}:${s.id || s.name}`,
2198
+ name: String(s.name || s.title || ''),
2199
+ description: String(s.description || s.desc || ''),
2200
+ source: src.name,
2201
+ author: s.author,
2202
+ category: String(s.category || 'custom'),
2203
+ tags: Array.isArray(s.tags) ? s.tags : [],
2204
+ downloads: Number(s.downloads || 0),
2205
+ installHint: String(s.installHint || ''),
2206
+ homepage: String(s.homepage || src.url),
2207
+ rawUrl: String(s.rawUrl || src.url),
2208
+ })));
2209
+ }
2210
+ }
2211
+ catch { /* skip failed sources */ }
2212
+ }
2213
+ }
2214
+ // 关键词过滤
2215
+ if (q) {
2216
+ const kw = q.toLowerCase();
2217
+ results = results.filter((s) => s.name.toLowerCase().includes(kw) ||
2218
+ s.description.toLowerCase().includes(kw) ||
2219
+ (s.tags || []).some((t) => t.toLowerCase().includes(kw)));
2220
+ }
2221
+ results.sort((a, b) => (b.downloads || 0) - (a.downloads || 0));
2222
+ res.json({ ok: true, total: results.length, skills: results.slice(0, limit) });
2223
+ });
2224
+ // 已安装技能持久化
2225
+ const installedSkillsFile = (0, path_1.join)(homeDir, 'skills', 'installed.json');
2226
+ app.post('/api/skills/install', async (req, res) => {
2227
+ const body = req.body;
2228
+ if (!body || !body.id || !body.name || !body.source) {
2229
+ res.status(400).json({ ok: false, error: '缺少 id / name / source 字段' });
2230
+ return;
2231
+ }
2232
+ try {
2233
+ const dir = (0, path_1.join)(homeDir, 'skills', body.id.replace(/[^a-zA-Z0-9_-]/g, '_'));
2234
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
2235
+ if (!saveJsonFile((0, path_1.join)(dir, 'meta.json'), { ...body, installedAt: new Date().toISOString() })) {
2236
+ res.status(500).json({ ok: false, error: '技能元数据写入失败' });
2237
+ return;
2238
+ }
2239
+ const installed = loadJsonFile(installedSkillsFile, []);
2240
+ if (!installed.find((s) => s.id === body.id)) {
2241
+ installed.push(body);
2242
+ if (!saveJsonFile(installedSkillsFile, installed)) {
2243
+ res.status(500).json({ ok: false, error: '已安装列表写入失败' });
2244
+ return;
2245
+ }
2246
+ }
2247
+ res.json({ ok: true, message: `技能「${body.name}」已记录为已安装` });
2248
+ }
2249
+ catch (e) {
2250
+ res.status(500).json({ ok: false, error: '安装失败: ' + e.message });
2251
+ }
2252
+ });
2253
+ app.get('/api/skills/installed', (_req, res) => {
2254
+ res.json({ ok: true, skills: loadJsonFile(installedSkillsFile, []) });
2255
+ });
2256
+ // 预置常用快捷指令(用户可一键运行,也可保存为自己的指令后编辑)
2257
+ const BUILTIN_AUTOMATIONS = [
2258
+ {
2259
+ id: 'builtin-code-review',
2260
+ name: '代码审查',
2261
+ icon: '🔍',
2262
+ category: '质量',
2263
+ goal: '请对当前工作区的代码做一次全面审查,重点检查:1) 潜在的 Bug 和逻辑漏洞;2) 代码规范和可读性问题;3) 安全风险;4) 性能瓶颈。按问题严重程度排序,给出具体的修改建议。',
2264
+ builtin: true,
2265
+ },
2266
+ {
2267
+ id: 'builtin-gen-test',
2268
+ name: '生成测试',
2269
+ icon: '🧪',
2270
+ category: '测试',
2271
+ goal: '请为当前项目生成单元测试,优先覆盖核心业务逻辑和边界条件。先分析项目结构和已有测试框架,然后为关键模块编写测试用例,确保测试可以直接运行。',
2272
+ builtin: true,
2273
+ },
2274
+ {
2275
+ id: 'builtin-build-check',
2276
+ name: '构建检查',
2277
+ icon: '🏗️',
2278
+ category: '工程',
2279
+ goal: '请运行项目的构建命令(如 npm run build / tsc),检查是否有编译错误或类型错误。如果有错误,逐一分析原因并给出修复方案,修复后重新验证构建是否通过。',
2280
+ builtin: true,
2281
+ },
2282
+ {
2283
+ id: 'builtin-dep-check',
2284
+ name: '依赖检查',
2285
+ icon: '📦',
2286
+ category: '工程',
2287
+ goal: '请检查当前项目的 package.json 依赖,分析:1) 是否有过时的依赖需要升级;2) 是否有已知安全漏洞的依赖;3) 是否有未使用的冗余依赖。给出升级建议和风险提示。',
2288
+ builtin: true,
2289
+ },
2290
+ {
2291
+ id: 'builtin-format',
2292
+ name: '代码格式化',
2293
+ icon: '✨',
2294
+ category: '工程',
2295
+ goal: '请对当前项目的代码进行全面格式化。先检查项目是否配置了 ESLint / Prettier 等工具,如果有就按配置格式化;如果没有,就按通用规范统一缩进、引号、分号等风格。格式化后验证构建是否正常。',
2296
+ builtin: true,
2297
+ },
2298
+ {
2299
+ id: 'builtin-gen-doc',
2300
+ name: '生成文档',
2301
+ icon: '📝',
2302
+ category: '文档',
2303
+ goal: '请为当前项目生成一份完整的 README 文档,包含:项目简介、功能特性、技术栈、安装步骤、使用方法、目录结构说明、开发指南。如果项目有 API,也一并生成 API 文档。',
2304
+ builtin: true,
2305
+ },
2306
+ {
2307
+ id: 'builtin-refactor',
2308
+ name: '重构优化',
2309
+ icon: '♻️',
2310
+ category: '质量',
2311
+ goal: '请对当前项目的代码进行重构优化,重点关注:1) 重复代码提取;2) 过长函数拆分;3) 命名规范统一;4) 复杂逻辑简化。重构过程中确保不改变外部行为,重构后验证构建和测试通过。',
2312
+ builtin: true,
2313
+ },
2314
+ {
2315
+ id: 'builtin-release',
2316
+ name: '版本发布准备',
2317
+ icon: '🚀',
2318
+ category: '工程',
2319
+ goal: '请为当前项目准备一次版本发布,包括:1) 检查 package.json 版本号并建议升级;2) 整理 CHANGELOG 变更日志;3) 运行构建和测试确保通过;4) 检查是否有未提交的代码。给出完整的发布检查清单。',
2320
+ builtin: true,
2321
+ },
2322
+ {
2323
+ id: 'builtin-security',
2324
+ name: '安全扫描',
2325
+ icon: '🛡️',
2326
+ category: '安全',
2327
+ goal: '请对当前项目的代码进行安全扫描,重点检查:1) SQL 注入、XSS、CSRF 等常见漏洞;2) 硬编码的密钥或敏感信息;3) 不安全的依赖;4) 权限校验缺失。按风险等级排序,给出修复建议。',
2328
+ builtin: true,
2329
+ },
2330
+ {
2331
+ id: 'builtin-perf',
2332
+ name: '性能分析',
2333
+ icon: '⚡',
2334
+ category: '质量',
2335
+ goal: '请分析当前项目的性能瓶颈,重点关注:1) 慢查询和低效算法;2) 不必要的重复计算;3) 内存泄漏风险;4) 异步操作阻塞。给出具体的优化方案和预期收益,优化后验证效果。',
2336
+ builtin: true,
2337
+ },
2338
+ ];
2339
+ const automationsFile = (0, path_1.join)(homeDir, 'automations.json');
2340
+ app.get('/api/automations', (_req, res) => {
2341
+ const user = loadJsonFile(automationsFile, []);
2342
+ res.json({ ok: true, builtin: BUILTIN_AUTOMATIONS, automations: user });
2343
+ });
2344
+ app.post('/api/automations', async (req, res) => {
2345
+ const body = (req.body ?? {});
2346
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
2347
+ const goal = typeof body.goal === 'string' ? body.goal.trim() : '';
2348
+ if (!name || !goal) {
2349
+ res.status(400).json({ ok: false, error: '缺少 name 或 goal 字段' });
2350
+ return;
2351
+ }
2352
+ const list = loadJsonFile(automationsFile, []);
2353
+ const rule = {
628
2354
  id: `auto_${Date.now()}_${(0, crypto_1.randomUUID)().slice(0, 4)}`,
629
2355
  name,
630
2356
  goal,
@@ -658,7 +2384,16 @@ function startWebServer(opts = {}) {
658
2384
  });
659
2385
  app.post('/api/automations/:id/run', async (req, res) => {
660
2386
  const list = loadJsonFile(automationsFile, []);
661
- const rule = list.find((a) => a.id === req.params.id);
2387
+ // 先从用户自定义指令里找,找不到再从预置指令里找
2388
+ let rule = list.find((a) => a.id === req.params.id);
2389
+ let isBuiltin = false;
2390
+ if (!rule) {
2391
+ const builtin = BUILTIN_AUTOMATIONS.find((a) => a.id === req.params.id);
2392
+ if (builtin) {
2393
+ rule = builtin;
2394
+ isBuiltin = true;
2395
+ }
2396
+ }
662
2397
  if (!rule) {
663
2398
  res.status(404).json({ ok: false, error: '指令不存在' });
664
2399
  return;
@@ -669,12 +2404,16 @@ function startWebServer(opts = {}) {
669
2404
  });
670
2405
  rule.runCount += 1;
671
2406
  rule.lastRunAt = new Date().toISOString();
672
- // 任务已入队,runCount 持久化失败不阻塞响应(带 persistWarning 提示)
673
- const persistWarning = !saveJsonFile(automationsFile, list);
2407
+ // 预置指令的 runCount 不持久化(只在内存中统计),用户自定义指令才持久化
2408
+ let persistWarning = false;
2409
+ if (!isBuiltin) {
2410
+ persistWarning = !saveJsonFile(automationsFile, list);
2411
+ }
674
2412
  res.status(201).json({
675
2413
  ok: true,
676
2414
  task: publicTask(record, true),
677
2415
  runCount: rule.runCount,
2416
+ builtin: isBuiltin || undefined,
678
2417
  persistWarning: persistWarning || undefined,
679
2418
  });
680
2419
  });
@@ -737,9 +2476,43 @@ function startWebServer(opts = {}) {
737
2476
  },
738
2477
  ];
739
2478
  const templatesFile = (0, path_1.join)(homeDir, 'templates.json');
740
- app.get('/api/templates', (_req, res) => {
2479
+ app.get('/api/templates', async (_req, res) => {
741
2480
  const user = loadJsonFile(templatesFile, []);
742
- res.json({ ok: true, builtin: BUILTIN_TEMPLATES, user });
2481
+ // 从节点同步的模板
2482
+ const nodeTemplates = collectNodeTemplates().map((t) => ({
2483
+ id: `node:${t.id}`,
2484
+ title: t.title,
2485
+ category: t.category,
2486
+ goal: t.goal,
2487
+ icon: t.icon,
2488
+ builtin: false,
2489
+ source: t.source,
2490
+ nodeId: t.nodeId,
2491
+ }));
2492
+ // 从自定义来源拉取模板
2493
+ const customTemplates = [];
2494
+ for (const src of loadSources()) {
2495
+ if (!src.enabled || src.type !== 'templates')
2496
+ continue;
2497
+ try {
2498
+ const r = await fetchWithRetry(src.url, 10000, 0);
2499
+ if (r.ok) {
2500
+ const data = await r.json();
2501
+ const list = Array.isArray(data) ? data : (data.items || data.templates || []);
2502
+ customTemplates.push(...list.map((t) => ({
2503
+ id: `custom:${src.id}:${t.id || t.title}`,
2504
+ title: String(t.title || t.name || ''),
2505
+ category: String(t.category || src.name),
2506
+ goal: String(t.goal || t.prompt || ''),
2507
+ icon: String(t.icon || '📄'),
2508
+ builtin: false,
2509
+ source: src.name,
2510
+ })));
2511
+ }
2512
+ }
2513
+ catch { /* skip */ }
2514
+ }
2515
+ res.json({ ok: true, builtin: BUILTIN_TEMPLATES, user, node: nodeTemplates, custom: customTemplates });
743
2516
  });
744
2517
  app.post('/api/templates', async (req, res) => {
745
2518
  const body = (req.body ?? {});
@@ -778,54 +2551,96 @@ function startWebServer(opts = {}) {
778
2551
  }
779
2552
  res.json({ ok: true });
780
2553
  });
781
- /* ========== 办公助理:内置能力清单 ========== */
782
- app.get('/api/office/capabilities', (_req, res) => {
2554
+ /* ========== 办公助理:内置能力清单 + 节点能力 + 自定义来源 ========== */
2555
+ app.get('/api/office/capabilities', async (_req, res) => {
2556
+ const builtin = [
2557
+ {
2558
+ id: 'doc-summary',
2559
+ icon: '📄',
2560
+ title: '文档摘要',
2561
+ desc: '粘贴长文档,生成结构化摘要与要点',
2562
+ prompt: '请对以下文档做摘要,提取 3-5 个核心要点并列出待办:\n\n',
2563
+ source: '内置',
2564
+ },
2565
+ {
2566
+ id: 'doc-translate',
2567
+ icon: '🌐',
2568
+ title: '中英互译',
2569
+ desc: '技术文档/代码注释翻译',
2570
+ prompt: '请将以下内容准确翻译(保留代码与术语):\n\n',
2571
+ source: '内置',
2572
+ },
2573
+ {
2574
+ id: 'doc-rewrite',
2575
+ icon: '✍️',
2576
+ title: '润色改写',
2577
+ desc: '把草稿改写为正式/简洁风格',
2578
+ prompt: '请润色以下文本,使其更专业简洁:\n\n',
2579
+ source: '内置',
2580
+ },
2581
+ {
2582
+ id: 'meeting-notes',
2583
+ icon: '📋',
2584
+ title: '会议纪要',
2585
+ desc: '从聊天/录音转写生成行动项',
2586
+ prompt: '请从以下记录中提取:决策、负责人、截止时间、待办:\n\n',
2587
+ source: '内置',
2588
+ },
2589
+ {
2590
+ id: 'email-draft',
2591
+ icon: '✉️',
2592
+ title: '邮件起草',
2593
+ desc: '按要点生成工作邮件',
2594
+ prompt: '请起草一封邮件,主题/背景/诉求如下:\n\n',
2595
+ source: '内置',
2596
+ },
2597
+ {
2598
+ id: 'excel-formula',
2599
+ icon: '📊',
2600
+ title: '表格公式',
2601
+ desc: '描述需求生成 Excel/Sheets 公式',
2602
+ prompt: '请写出实现以下需求的表格公式并解释:\n\n',
2603
+ source: '内置',
2604
+ },
2605
+ ];
2606
+ // 从节点同步的办公能力
2607
+ const nodeCaps = collectNodeOffice().map((c) => ({
2608
+ id: `node:${c.id}`,
2609
+ icon: c.icon,
2610
+ title: c.title,
2611
+ desc: c.desc,
2612
+ prompt: c.prompt,
2613
+ source: c.source,
2614
+ nodeId: c.nodeId,
2615
+ }));
2616
+ // 从自定义来源拉取办公能力
2617
+ const customCaps = [];
2618
+ for (const src of loadSources()) {
2619
+ if (!src.enabled || src.type !== 'office')
2620
+ continue;
2621
+ try {
2622
+ const r = await fetchWithRetry(src.url, 10000, 0);
2623
+ if (r.ok) {
2624
+ const data = await r.json();
2625
+ const list = Array.isArray(data) ? data : (data.items || data.capabilities || []);
2626
+ customCaps.push(...list.map((c) => ({
2627
+ id: `custom:${src.id}:${c.id || c.title}`,
2628
+ icon: String(c.icon || '🔧'),
2629
+ title: String(c.title || c.name || ''),
2630
+ desc: String(c.desc || c.description || ''),
2631
+ prompt: String(c.prompt || c.goal || ''),
2632
+ source: src.name,
2633
+ })));
2634
+ }
2635
+ }
2636
+ catch { /* skip */ }
2637
+ }
783
2638
  res.json({
784
2639
  ok: true,
785
- capabilities: [
786
- {
787
- id: 'doc-summary',
788
- icon: '📄',
789
- title: '文档摘要',
790
- desc: '粘贴长文档,生成结构化摘要与要点',
791
- prompt: '请对以下文档做摘要,提取 3-5 个核心要点并列出待办:\n\n',
792
- },
793
- {
794
- id: 'doc-translate',
795
- icon: '🌐',
796
- title: '中英互译',
797
- desc: '技术文档/代码注释翻译',
798
- prompt: '请将以下内容准确翻译(保留代码与术语):\n\n',
799
- },
800
- {
801
- id: 'doc-rewrite',
802
- icon: '✍️',
803
- title: '润色改写',
804
- desc: '把草稿改写为正式/简洁风格',
805
- prompt: '请润色以下文本,使其更专业简洁:\n\n',
806
- },
807
- {
808
- id: 'meeting-notes',
809
- icon: '📋',
810
- title: '会议纪要',
811
- desc: '从聊天/录音转写生成行动项',
812
- prompt: '请从以下记录中提取:决策、负责人、截止时间、待办:\n\n',
813
- },
814
- {
815
- id: 'email-draft',
816
- icon: '✉️',
817
- title: '邮件起草',
818
- desc: '按要点生成工作邮件',
819
- prompt: '请起草一封邮件,主题/背景/诉求如下:\n\n',
820
- },
821
- {
822
- id: 'excel-formula',
823
- icon: '📊',
824
- title: '表格公式',
825
- desc: '描述需求生成 Excel/Sheets 公式',
826
- prompt: '请写出实现以下需求的表格公式并解释:\n\n',
827
- },
828
- ],
2640
+ capabilities: [...builtin, ...nodeCaps, ...customCaps],
2641
+ builtin,
2642
+ node: nodeCaps,
2643
+ custom: customCaps,
829
2644
  });
830
2645
  });
831
2646
  /* ========== 大模型配置(三重加密:apiKey AES-256-GCM 落盘加密 + RSA 加密传输) ========== */
@@ -856,6 +2671,8 @@ function startWebServer(opts = {}) {
856
2671
  apiKey: resolveApiKey(m),
857
2672
  model: m.name,
858
2673
  })));
2674
+ // P2: 同步更新补全引擎
2675
+ rebuildCompletionEngine(encList);
859
2676
  return ok;
860
2677
  }
861
2678
  // 初始化模型提供列表,并注册到任务队列
@@ -867,6 +2684,52 @@ function startWebServer(opts = {}) {
867
2684
  apiKey: resolveApiKey(m),
868
2685
  model: m.name,
869
2686
  })));
2687
+ /* ========== P2: 共享 ModelRouter + CompletionEngine(供 /api/completion 使用) ========== */
2688
+ let sharedModelRouter = null;
2689
+ let completionEngine = null;
2690
+ // P2-1: 设计稿转代码引擎(多模态)
2691
+ let designToCodeEngine = null;
2692
+ function rebuildCompletionEngine(models) {
2693
+ try {
2694
+ const providers = models
2695
+ .filter((m) => m.apiBase && m.name)
2696
+ .map((m) => new openai_compatible_provider_1.OpenAICompatibleProvider({
2697
+ id: m.id,
2698
+ type: 'openai-compatible',
2699
+ baseURL: m.apiBase,
2700
+ apiKey: resolveApiKey(m),
2701
+ model: m.name,
2702
+ tags: ['code-gen', 'cheap'],
2703
+ }));
2704
+ if (!providers.length) {
2705
+ sharedModelRouter = null;
2706
+ completionEngine = null;
2707
+ designToCodeEngine = null;
2708
+ return;
2709
+ }
2710
+ sharedModelRouter = new model_router_1.ModelRouter(providers, 'capability', 1.0);
2711
+ // 阶段一-1:构建代码图谱,用于跨文件上下文注入
2712
+ const codeGraph = (0, symbol_index_1.buildCodeGraph)(serverWorkspaceDir, { maxFiles: 500 });
2713
+ completionEngine = (0, completion_engine_1.createCompletionEngine)(sharedModelRouter, {
2714
+ maxPrefixChars: 1500,
2715
+ maxSuffixChars: 500,
2716
+ quickMaxTokens: 64,
2717
+ fullMaxTokens: 256,
2718
+ cacheTtlMs: 10000,
2719
+ enableSyntaxCheck: true,
2720
+ }, codeGraph);
2721
+ // P2-1: 更新设计稿转代码引擎的模型提供者
2722
+ designToCodeEngine = (0, design_to_code_1.createDesignToCodeEngine)(providers);
2723
+ }
2724
+ catch (e) {
2725
+ console.error('[completion] rebuild failed:', e);
2726
+ sharedModelRouter = null;
2727
+ completionEngine = null;
2728
+ designToCodeEngine = null;
2729
+ }
2730
+ }
2731
+ // 初始化时构建
2732
+ rebuildCompletionEngine(initialModels);
870
2733
  // 第二重(通信层):向客户端下发 RSA 公钥,用于加密敏感参数(如模型 API Key)传输
871
2734
  app.get('/api/security/public-key', (_req, res) => {
872
2735
  res.json({ ok: true, publicKey: rsaKeys.publicKey, algorithm: 'RSA-OAEP-2048-SHA256' });