coding-tool-x 3.8.4 → 3.8.5

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 (434) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
  10. package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-D7CCOFQC.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  29. package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
  34. package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
  36. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  37. package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
  45. package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
  51. package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
  52. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
  59. package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
  62. package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  64. package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-C0EHRym0.css +1 -0
  71. package/dist/web/assets/index-DnEocV0M.js +2 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +8 -4
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +94 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +162 -0
  140. package/src/platforms/channel-auth-service.js +82 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
  269. package/src/{server/services → platforms}/notification-hooks.js +140 -1023
  270. package/src/platforms/oauth-credentials-service.js +506 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +11 -20
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +1 -1
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +780 -213
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -0,0 +1,678 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const http = require('http');
5
+ const https = require('https');
6
+ const crypto = require('crypto');
7
+ const { HttpsProxyAgent } = require('https-proxy-agent');
8
+
9
+ const REMOTE_PROVIDER_TYPES = Object.freeze([
10
+ 'wechatBot',
11
+ 'qqBot',
12
+ 'feishuBot',
13
+ 'wecomBot',
14
+ 'dingtalkBot',
15
+ 'telegramBot'
16
+ ]);
17
+
18
+ const PROVIDER_METADATA = Object.freeze({
19
+ wechatBot: Object.freeze({
20
+ label: '微信',
21
+ legacyName: '微信 Bot',
22
+ description: '使用个人微信 iLink token 发送通知',
23
+ hint: '首次 token 可由 GA 微信扫码生成;也可以直接填写 token。',
24
+ defaults: Object.freeze({ tokenFile: '~/.wxbot/token.json', botToken: '', targetUserId: '', contextToken: '' }),
25
+ fields: Object.freeze([
26
+ Object.freeze({ key: 'tokenFile', label: 'token.json 路径', type: 'input', placeholder: '~/.wxbot/token.json', wide: true }),
27
+ Object.freeze({ key: 'botToken', label: 'Token', type: 'secret', placeholder: '可直接填写 token', wide: true }),
28
+ Object.freeze({ key: 'targetUserId', label: '接收用户 ID', type: 'input', placeholder: '微信用户 ID' }),
29
+ Object.freeze({ key: 'contextToken', label: 'Context Token(可选)', type: 'secret', placeholder: '可选' })
30
+ ])
31
+ }),
32
+ qqBot: Object.freeze({
33
+ label: 'QQ',
34
+ legacyName: 'QQ Bot',
35
+ description: '通过 OneBot / NapCat / go-cqhttp 兼容 HTTP 接口发送通知',
36
+ hint: 'GA 当前构建已移除 QQ 前端,这里按 OneBot 兼容桥接入。',
37
+ defaults: Object.freeze({ endpoint: 'http://127.0.0.1:3000', accessToken: '', targetType: 'private', targetId: '' }),
38
+ fields: Object.freeze([
39
+ Object.freeze({ key: 'endpoint', label: 'OneBot HTTP 地址', type: 'input', placeholder: 'http://127.0.0.1:3000', wide: true }),
40
+ Object.freeze({ key: 'accessToken', label: 'Access Token(可选)', type: 'secret', placeholder: '可选' }),
41
+ Object.freeze({ key: 'targetType', label: '接收类型', type: 'select', options: Object.freeze([
42
+ Object.freeze({ label: '私聊', value: 'private' }),
43
+ Object.freeze({ label: '群聊', value: 'group' })
44
+ ]) }),
45
+ Object.freeze({ key: 'targetId', label: '接收对象 ID', type: 'input', placeholder: 'QQ 号或群号' })
46
+ ])
47
+ }),
48
+ feishuBot: Object.freeze({
49
+ label: '飞书',
50
+ legacyName: '飞书 Bot',
51
+ description: '通过飞书自定义机器人 Webhook 发送通知',
52
+ hint: '填写飞书自定义机器人 Webhook URL。',
53
+ defaults: Object.freeze({ webhookUrl: '' }),
54
+ fields: Object.freeze([
55
+ Object.freeze({ key: 'webhookUrl', label: 'Webhook URL', type: 'input', placeholder: 'https://open.feishu.cn/open-apis/bot/v2/hook/...', wide: true })
56
+ ])
57
+ }),
58
+ wecomBot: Object.freeze({
59
+ label: '企业微信',
60
+ legacyName: '企业微信 Bot',
61
+ description: '通过企业微信群机器人 Webhook 发送通知',
62
+ hint: '当前通知发送使用企业微信群机器人 Webhook;GA 的 bot_id / secret 长连接模式不适合单向通知。',
63
+ defaults: Object.freeze({ webhookUrl: '' }),
64
+ fields: Object.freeze([
65
+ Object.freeze({ key: 'webhookUrl', label: 'Webhook URL', type: 'input', placeholder: '企业微信群机器人 Webhook URL', wide: true })
66
+ ])
67
+ }),
68
+ dingtalkBot: Object.freeze({
69
+ label: '钉钉',
70
+ legacyName: '钉钉 Bot',
71
+ description: '支持钉钉自定义机器人 Webhook 和 GA 同款 App 模式',
72
+ hint: 'App 模式需要 App Key / App Secret,并填写用户 ID 或群会话 ID。',
73
+ defaults: Object.freeze({ mode: 'webhook', webhookUrl: '', clientId: '', clientSecret: '', targetType: 'group', targetId: '' }),
74
+ fields: Object.freeze([
75
+ Object.freeze({ key: 'mode', label: '接入模式', type: 'select', options: Object.freeze([
76
+ Object.freeze({ label: 'Webhook', value: 'webhook' }),
77
+ Object.freeze({ label: 'GA App 模式', value: 'app' })
78
+ ]) }),
79
+ Object.freeze({ key: 'clientId', label: 'App Key', type: 'input', placeholder: 'Client ID', visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
80
+ Object.freeze({ key: 'clientSecret', label: 'App Secret', type: 'secret', placeholder: 'Secret', visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
81
+ Object.freeze({ key: 'targetType', label: '接收类型', type: 'select', options: Object.freeze([
82
+ Object.freeze({ label: '群会话', value: 'group' }),
83
+ Object.freeze({ label: '用户', value: 'user' })
84
+ ]), visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
85
+ Object.freeze({ key: 'targetId', label: '接收对象 ID', type: 'input', placeholder: '用户 ID 或群会话 ID', visibleWhen: Object.freeze({ key: 'mode', equals: 'app' }) }),
86
+ Object.freeze({ key: 'webhookUrl', label: 'Webhook URL', type: 'input', placeholder: '钉钉自定义机器人 Webhook URL', wide: true, visibleWhen: Object.freeze({ key: 'mode', equals: 'webhook' }) })
87
+ ])
88
+ }),
89
+ telegramBot: Object.freeze({
90
+ label: 'Telegram',
91
+ legacyName: 'Telegram Bot',
92
+ description: '通过 Telegram Bot API sendMessage 发送通知',
93
+ hint: '需要 Token 和 Chat ID。',
94
+ defaults: Object.freeze({ botToken: '', chatId: '', proxy: '' }),
95
+ fields: Object.freeze([
96
+ Object.freeze({ key: 'botToken', label: 'Token', type: 'secret', placeholder: 'Telegram token' }),
97
+ Object.freeze({ key: 'chatId', label: 'Chat ID', type: 'input', placeholder: 'Chat ID' }),
98
+ Object.freeze({ key: 'proxy', label: 'HTTP 代理(可选)', type: 'input', placeholder: 'http://127.0.0.1:7890', wide: true })
99
+ ])
100
+ })
101
+ });
102
+
103
+ const TYPE_ALIASES = Object.freeze({
104
+ wechat: 'wechatBot',
105
+ weixin: 'wechatBot',
106
+ qq: 'qqBot',
107
+ feishu: 'feishuBot',
108
+ lark: 'feishuBot',
109
+ wecom: 'wecomBot',
110
+ dingtalk: 'dingtalkBot',
111
+ telegram: 'telegramBot'
112
+ });
113
+
114
+ function trimString(value) {
115
+ return String(value || '').trim();
116
+ }
117
+
118
+ function normalizeProviderType(type) {
119
+ const value = trimString(type);
120
+ if (REMOTE_PROVIDER_TYPES.includes(value)) return value;
121
+ if (TYPE_ALIASES[value.toLowerCase()]) return TYPE_ALIASES[value.toLowerCase()];
122
+ return /^[a-z][a-z0-9_-]*$/i.test(value) ? value : '';
123
+ }
124
+
125
+ function createRemoteProviderId(type, index = 0) {
126
+ return `${type || 'provider'}-${Date.now().toString(36)}-${index}`;
127
+ }
128
+
129
+ function normalizeConfig(type, config = {}) {
130
+ const source = config && typeof config === 'object' && !Array.isArray(config) ? config : {};
131
+ switch (type) {
132
+ case 'wechatBot':
133
+ return {
134
+ tokenFile: trimString(source.tokenFile),
135
+ botToken: trimString(source.botToken),
136
+ targetUserId: trimString(source.targetUserId),
137
+ contextToken: trimString(source.contextToken)
138
+ };
139
+ case 'qqBot':
140
+ return {
141
+ endpoint: trimString(source.endpoint),
142
+ accessToken: trimString(source.accessToken),
143
+ targetType: source.targetType === 'group' ? 'group' : 'private',
144
+ targetId: trimString(source.targetId)
145
+ };
146
+ case 'feishuBot':
147
+ case 'wecomBot':
148
+ return { webhookUrl: trimString(source.webhookUrl) };
149
+ case 'dingtalkBot':
150
+ return {
151
+ mode: source.mode === 'app' ? 'app' : 'webhook',
152
+ webhookUrl: trimString(source.webhookUrl),
153
+ clientId: trimString(source.clientId),
154
+ clientSecret: trimString(source.clientSecret),
155
+ targetType: source.targetType === 'user' ? 'user' : 'group',
156
+ targetId: trimString(source.targetId)
157
+ };
158
+ case 'telegramBot':
159
+ return {
160
+ botToken: trimString(source.botToken),
161
+ chatId: trimString(source.chatId),
162
+ proxy: trimString(source.proxy)
163
+ };
164
+ default:
165
+ return {};
166
+ }
167
+ }
168
+
169
+ function createValidationError(message) {
170
+ const error = new Error(message);
171
+ error.statusCode = 400;
172
+ return error;
173
+ }
174
+
175
+ function validateFeishuWebhookUrl(webhookUrl) {
176
+ const value = trimString(webhookUrl);
177
+ if (!value) return null;
178
+ let urlObj;
179
+ try {
180
+ urlObj = new URL(value);
181
+ } catch (error) {
182
+ throw createValidationError('飞书 Webhook URL 格式不正确');
183
+ }
184
+ if (urlObj.protocol !== 'https:') throw createValidationError('飞书 Webhook 必须使用 HTTPS');
185
+ if (urlObj.hostname !== 'open.feishu.cn') throw createValidationError('仅支持 open.feishu.cn 的飞书 Webhook');
186
+ return urlObj;
187
+ }
188
+
189
+ function validateHttpsUrl(value, label, options = {}) {
190
+ const raw = trimString(value);
191
+ if (!raw) return null;
192
+ let urlObj;
193
+ try {
194
+ urlObj = new URL(raw);
195
+ } catch (error) {
196
+ throw createValidationError(`${label} 格式不正确`);
197
+ }
198
+ if (options.requireHttps !== false && urlObj.protocol !== 'https:') {
199
+ throw createValidationError(`${label} 必须使用 HTTPS`);
200
+ }
201
+ if (options.hostname && urlObj.hostname !== options.hostname) {
202
+ throw createValidationError(`${label} 仅支持 ${options.hostname}`);
203
+ }
204
+ return urlObj;
205
+ }
206
+
207
+ function requireConfigValue(config, key, label) {
208
+ if (!trimString(config?.[key])) throw createValidationError(`请填写${label}`);
209
+ }
210
+
211
+ function validateConfig(type, config = {}) {
212
+ switch (type) {
213
+ case 'wechatBot':
214
+ if (!trimString(config.botToken) && !trimString(config.tokenFile)) {
215
+ throw createValidationError('请填写微信 Bot Token 或 token.json 路径');
216
+ }
217
+ requireConfigValue(config, 'targetUserId', '微信接收用户 ID');
218
+ break;
219
+ case 'qqBot':
220
+ requireConfigValue(config, 'endpoint', 'QQ Bot HTTP 地址');
221
+ validateHttpsUrl(config.endpoint, 'QQ Bot HTTP 地址', { requireHttps: false });
222
+ requireConfigValue(config, 'targetId', 'QQ 接收对象 ID');
223
+ break;
224
+ case 'feishuBot':
225
+ validateFeishuWebhookUrl(config.webhookUrl);
226
+ requireConfigValue(config, 'webhookUrl', '飞书 Webhook URL');
227
+ break;
228
+ case 'wecomBot':
229
+ validateHttpsUrl(config.webhookUrl, '企业微信 Webhook URL');
230
+ requireConfigValue(config, 'webhookUrl', '企业微信 Webhook URL');
231
+ break;
232
+ case 'dingtalkBot':
233
+ if (config.mode === 'app') {
234
+ requireConfigValue(config, 'clientId', '钉钉 App Key');
235
+ requireConfigValue(config, 'clientSecret', '钉钉 App Secret');
236
+ requireConfigValue(config, 'targetId', '钉钉接收对象 ID');
237
+ } else {
238
+ validateHttpsUrl(config.webhookUrl, '钉钉 Webhook URL');
239
+ requireConfigValue(config, 'webhookUrl', '钉钉 Webhook URL');
240
+ }
241
+ break;
242
+ case 'telegramBot':
243
+ requireConfigValue(config, 'botToken', 'Telegram Bot Token');
244
+ requireConfigValue(config, 'chatId', 'Telegram Chat ID');
245
+ break;
246
+ default:
247
+ throw createValidationError('远程通知渠道类型不正确');
248
+ }
249
+ }
250
+
251
+ function normalizeProvider(provider = {}, index = 0, descriptors = PROVIDER_METADATA) {
252
+ const type = normalizeProviderType(provider.type);
253
+ const metadata = descriptors[type];
254
+ if (!metadata) return null;
255
+ const suppliedName = trimString(provider.name);
256
+ return {
257
+ id: suppliedName && trimString(provider.id) ? trimString(provider.id) : trimString(provider.id) || createRemoteProviderId(type, index),
258
+ type,
259
+ name: suppliedName && suppliedName !== metadata.legacyName ? suppliedName : metadata.label,
260
+ enabled: provider.enabled === true,
261
+ config: normalizeConfig(type, provider.config)
262
+ };
263
+ }
264
+
265
+ function normalizeRemoteNotificationsConfig(remoteNotifications = {}, descriptors = PROVIDER_METADATA) {
266
+ const source = remoteNotifications && typeof remoteNotifications === 'object' && !Array.isArray(remoteNotifications)
267
+ ? remoteNotifications
268
+ : {};
269
+ const providers = Array.isArray(source.providers)
270
+ ? source.providers.map((provider, index) => normalizeProvider(provider, index, descriptors)).filter(Boolean)
271
+ : [];
272
+ return { providers };
273
+ }
274
+
275
+ function buildTestNotificationContext() {
276
+ return {
277
+ title: 'Coding Tool - 测试通知',
278
+ message: '这是一条测试通知',
279
+ source: 'test',
280
+ eventType: 'test',
281
+ timestamp: new Date().toLocaleString('zh-CN'),
282
+ hostname: require('os').hostname()
283
+ };
284
+ }
285
+
286
+ function formatProviderText(ctx) {
287
+ return [
288
+ ctx.title,
289
+ `来源: ${ctx.source}`,
290
+ `状态: ${ctx.message}`,
291
+ `时间: ${ctx.timestamp}`,
292
+ `设备: ${ctx.hostname}`
293
+ ].join('\n');
294
+ }
295
+
296
+ function createClientId(prefix = 'coding-tool') {
297
+ const randomPart = typeof crypto.randomUUID === 'function'
298
+ ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)
299
+ : crypto.randomBytes(8).toString('hex');
300
+ return `${prefix}-${randomPart}`;
301
+ }
302
+
303
+ function sendFeishuTest(webhookUrl) {
304
+ return new Promise((resolve, reject) => {
305
+ try {
306
+ const urlObj = validateFeishuWebhookUrl(webhookUrl);
307
+ const data = JSON.stringify({
308
+ msg_type: 'interactive',
309
+ card: {
310
+ header: {
311
+ title: { tag: 'plain_text', content: 'Coding Tool - 测试通知' },
312
+ template: 'blue'
313
+ },
314
+ elements: [
315
+ { tag: 'div', text: { tag: 'lark_md', content: '**状态**: 这是一条测试通知' } },
316
+ { tag: 'div', text: { tag: 'lark_md', content: '**时间**: ' + new Date().toLocaleString('zh-CN') } }
317
+ ]
318
+ }
319
+ });
320
+ const requestModule = urlObj.protocol === 'https:' ? https : http;
321
+ const request = requestModule.request({
322
+ hostname: urlObj.hostname,
323
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
324
+ path: urlObj.pathname + urlObj.search,
325
+ method: 'POST',
326
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
327
+ timeout: 10000
328
+ }, () => resolve());
329
+ request.on('error', reject);
330
+ request.on('timeout', () => request.destroy(new Error('飞书测试通知超时')));
331
+ request.write(data);
332
+ request.end();
333
+ } catch (error) {
334
+ reject(error);
335
+ }
336
+ });
337
+ }
338
+
339
+ function requestJson(url, data, headers = {}, extraOptions = {}) {
340
+ return new Promise((resolve, reject) => {
341
+ try {
342
+ const urlObj = new URL(url);
343
+ const body = JSON.stringify(data);
344
+ const requestModule = urlObj.protocol === 'https:' ? https : http;
345
+ const request = requestModule.request({
346
+ hostname: urlObj.hostname,
347
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
348
+ path: urlObj.pathname + urlObj.search,
349
+ method: 'POST',
350
+ timeout: 10000,
351
+ headers: {
352
+ 'Content-Type': 'application/json',
353
+ 'Content-Length': Buffer.byteLength(body),
354
+ ...headers
355
+ },
356
+ ...extraOptions
357
+ }, response => {
358
+ response.resume();
359
+ if (response.statusCode >= 200 && response.statusCode < 300) resolve();
360
+ else reject(new Error(`远程通知返回 HTTP ${response.statusCode}`));
361
+ });
362
+ request.on('error', reject);
363
+ request.on('timeout', () => request.destroy(new Error('远程通知测试超时')));
364
+ request.write(body);
365
+ request.end();
366
+ } catch (error) {
367
+ reject(error);
368
+ }
369
+ });
370
+ }
371
+
372
+ function requestDingTalkAccessToken(config = {}) {
373
+ if (!config.clientId || !config.clientSecret) return Promise.resolve('');
374
+ return new Promise((resolve, reject) => {
375
+ try {
376
+ const body = JSON.stringify({ appKey: config.clientId, appSecret: config.clientSecret });
377
+ const request = https.request({
378
+ hostname: 'api.dingtalk.com',
379
+ path: '/v1.0/oauth2/accessToken',
380
+ method: 'POST',
381
+ timeout: 10000,
382
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
383
+ }, response => {
384
+ let responseBody = '';
385
+ response.setEncoding('utf8');
386
+ response.on('data', chunk => { responseBody += chunk; });
387
+ response.on('end', () => {
388
+ try {
389
+ const parsed = JSON.parse(responseBody);
390
+ if (response.statusCode >= 200 && response.statusCode < 300 && parsed.accessToken) {
391
+ resolve(parsed.accessToken);
392
+ return;
393
+ }
394
+ reject(new Error(`钉钉 Access Token 获取失败: ${responseBody.slice(0, 300)}`));
395
+ } catch (error) {
396
+ reject(error);
397
+ }
398
+ });
399
+ });
400
+ request.on('error', reject);
401
+ request.on('timeout', () => request.destroy(new Error('钉钉 Access Token 获取超时')));
402
+ request.write(body);
403
+ request.end();
404
+ } catch (error) {
405
+ reject(error);
406
+ }
407
+ });
408
+ }
409
+
410
+ function requestTelegramMessage(config = {}, ctx) {
411
+ const url = `https://api.telegram.org/bot${config.botToken}/sendMessage`;
412
+ const payload = { chat_id: config.chatId, text: formatProviderText(ctx) };
413
+ const agent = config.proxy ? new HttpsProxyAgent(config.proxy) : null;
414
+ return requestJson(url, payload, {}, agent ? { agent } : {});
415
+ }
416
+
417
+ async function sendDingTalkAppProviderTest(config = {}, ctx) {
418
+ const token = await requestDingTalkAccessToken(config);
419
+ if (!token) throw createValidationError('钉钉 App Access Token 获取失败');
420
+ const isGroup = config.targetType !== 'user';
421
+ const payload = {
422
+ robotCode: config.clientId,
423
+ msgKey: 'sampleMarkdown',
424
+ msgParam: JSON.stringify({
425
+ title: ctx.title,
426
+ text: `### ${ctx.title}\n\n- 状态: ${ctx.message}\n- 时间: ${ctx.timestamp}\n- 设备: ${ctx.hostname}`
427
+ })
428
+ };
429
+ if (isGroup) payload.openConversationId = config.targetId;
430
+ else payload.userIds = [config.targetId];
431
+ return requestJson(
432
+ 'https://api.dingtalk.com/v1.0/robot/' + (isGroup ? 'groupMessages/send' : 'oToMessages/batchSend'),
433
+ payload,
434
+ { 'x-acs-dingtalk-access-token': token }
435
+ );
436
+ }
437
+
438
+ function createProviderSenders() {
439
+ const getContext = () => buildTestNotificationContext();
440
+ return {
441
+ feishuBot: provider => sendFeishuTest(provider.config.webhookUrl),
442
+ qqBot: provider => {
443
+ const config = provider.config;
444
+ const ctx = getContext();
445
+ const pathSuffix = config.targetType === 'group' ? '/send_group_msg' : '/send_private_msg';
446
+ return requestJson(String(config.endpoint).replace(/\/$/, '') + pathSuffix, {
447
+ [config.targetType === 'group' ? 'group_id' : 'user_id']: config.targetId,
448
+ message: formatProviderText(ctx)
449
+ }, config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {});
450
+ },
451
+ wecomBot: provider => {
452
+ const ctx = getContext();
453
+ return requestJson(provider.config.webhookUrl, {
454
+ msgtype: 'markdown',
455
+ markdown: { content: `**${ctx.title}**\\n> 状态: ${ctx.message}\\n> 时间: ${ctx.timestamp}` }
456
+ });
457
+ },
458
+ dingtalkBot: provider => {
459
+ const ctx = getContext();
460
+ return provider.config.mode === 'app'
461
+ ? sendDingTalkAppProviderTest(provider.config, ctx)
462
+ : requestJson(provider.config.webhookUrl, {
463
+ msgtype: 'markdown',
464
+ markdown: { title: ctx.title, text: `### ${ctx.title}\\n\\n- 状态: ${ctx.message}\\n- 时间: ${ctx.timestamp}` }
465
+ });
466
+ },
467
+ telegramBot: provider => requestTelegramMessage(provider.config, getContext()),
468
+ wechatBot: provider => {
469
+ const config = { ...provider.config };
470
+ const ctx = getContext();
471
+ if (!config.botToken && config.tokenFile) {
472
+ try {
473
+ const parsed = JSON.parse(fs.readFileSync(config.tokenFile, 'utf8'));
474
+ config.botToken = parsed.bot_token || parsed.token || '';
475
+ } catch (error) {
476
+ throw createValidationError('无法读取微信 token.json');
477
+ }
478
+ }
479
+ requireConfigValue(config, 'botToken', '微信 Bot Token');
480
+ return requestJson('https://ilinkai.weixin.qq.com/ilink/bot/sendmessage', {
481
+ msg: {
482
+ from_user_id: '',
483
+ to_user_id: config.targetUserId,
484
+ client_id: createClientId('coding-tool'),
485
+ message_type: 2,
486
+ message_state: 2,
487
+ item_list: [{ type: 1, text_item: { text: formatProviderText(ctx) } }],
488
+ ...(config.contextToken ? { context_token: config.contextToken } : {})
489
+ },
490
+ base_info: { channel_version: '2.1.8' }
491
+ }, {
492
+ AuthorizationType: 'ilink_bot_token',
493
+ Authorization: `Bearer ${config.botToken}`,
494
+ 'X-WECHAT-UIN': Buffer.from(String(Math.floor(Math.random() * 0xffffffff))).toString('base64')
495
+ });
496
+ }
497
+ };
498
+ }
499
+
500
+ function isAllowedField(field) {
501
+ return field && typeof field === 'object' && typeof field.key === 'string'
502
+ && typeof field.label === 'string' && ['input', 'secret', 'select'].includes(field.type);
503
+ }
504
+
505
+ function publicField(field) {
506
+ if (!isAllowedField(field)) return null;
507
+ const result = { key: field.key, label: field.label, type: field.type };
508
+ if (Array.isArray(field.options)) {
509
+ result.options = field.options
510
+ .filter(option => option && typeof option === 'object' && typeof option.label === 'string' && typeof option.value === 'string')
511
+ .map(option => ({ label: option.label, value: option.value }));
512
+ }
513
+ if (typeof field.placeholder === 'string') result.placeholder = field.placeholder;
514
+ if (typeof field.wide === 'boolean') result.wide = field.wide;
515
+ if (field.visibleWhen && typeof field.visibleWhen === 'object'
516
+ && typeof field.visibleWhen.key === 'string'
517
+ && Object.prototype.hasOwnProperty.call(field.visibleWhen, 'equals')
518
+ && ['string', 'number', 'boolean'].includes(typeof field.visibleWhen.equals)) {
519
+ result.visibleWhen = { key: field.visibleWhen.key, equals: field.visibleWhen.equals };
520
+ }
521
+ return result;
522
+ }
523
+
524
+ function publicDescriptor(descriptor) {
525
+ const fields = descriptor.fields.map(publicField).filter(Boolean);
526
+ const fieldKeys = new Set(fields.map(field => field.key));
527
+ const secretKeys = new Set(fields.filter(field => field.type === 'secret').map(field => field.key));
528
+ const defaults = Object.fromEntries(
529
+ Object.entries(descriptor.defaults)
530
+ .filter(([key]) => fieldKeys.has(key))
531
+ .map(([key, value]) => [
532
+ key,
533
+ secretKeys.has(key) ? '' : value
534
+ ])
535
+ );
536
+ return {
537
+ type: descriptor.type,
538
+ label: descriptor.label,
539
+ description: descriptor.description,
540
+ hint: descriptor.hint,
541
+ defaults,
542
+ fields
543
+ };
544
+ }
545
+
546
+ function createRemoteNotificationProviderRegistry({ senders = {} } = {}) {
547
+ const descriptors = new Map();
548
+ const builtInSenders = createProviderSenders();
549
+
550
+ function register(type, descriptor = {}) {
551
+ const normalizedType = normalizeProviderType(type || descriptor.type);
552
+ if (!normalizedType || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
553
+ throw new TypeError(`Invalid remote notification provider: ${type}`);
554
+ }
555
+ const metadata = PROVIDER_METADATA[normalizedType] || {};
556
+ const fields = Array.isArray(descriptor.fields) ? descriptor.fields.filter(isAllowedField) : [];
557
+ const normalizedDescriptor = {
558
+ type: normalizedType,
559
+ label: trimString(descriptor.label) || metadata.label || normalizedType,
560
+ description: trimString(descriptor.description),
561
+ hint: trimString(descriptor.hint),
562
+ defaults: descriptor.defaults && typeof descriptor.defaults === 'object' && !Array.isArray(descriptor.defaults)
563
+ ? { ...descriptor.defaults }
564
+ : { ...(metadata.defaults || {}) },
565
+ fields,
566
+ normalize: typeof descriptor.normalize === 'function' ? descriptor.normalize : config => normalizeConfig(normalizedType, config),
567
+ validate: typeof descriptor.validate === 'function' ? descriptor.validate : config => validateConfig(normalizedType, config),
568
+ send: typeof descriptor.send === 'function'
569
+ ? descriptor.send
570
+ : senders[normalizedType] || builtInSenders[normalizedType]
571
+ };
572
+ if (typeof normalizedDescriptor.send !== 'function') {
573
+ throw new TypeError(`Remote notification provider ${normalizedType} has no sender`);
574
+ }
575
+ descriptors.set(normalizedType, normalizedDescriptor);
576
+ return normalizedDescriptor;
577
+ }
578
+
579
+ for (const type of REMOTE_PROVIDER_TYPES) {
580
+ register(type, {
581
+ ...PROVIDER_METADATA[type],
582
+ normalize: config => normalizeConfig(type, config),
583
+ validate: config => validateConfig(type, config),
584
+ send: senders[type] || builtInSenders[type]
585
+ });
586
+ }
587
+
588
+ function descriptorFor(type) {
589
+ return descriptors.get(normalizeProviderType(type)) || null;
590
+ }
591
+
592
+ function normalizeProviderWithRegistry(provider = {}, index = 0) {
593
+ const type = normalizeProviderType(provider.type);
594
+ const descriptor = descriptorFor(type);
595
+ if (!descriptor) return null;
596
+ const suppliedName = trimString(provider.name);
597
+ return {
598
+ id: trimString(provider.id) || createRemoteProviderId(type, index),
599
+ type,
600
+ name: suppliedName && suppliedName !== PROVIDER_METADATA[type]?.legacyName ? suppliedName : descriptor.label,
601
+ enabled: provider.enabled === true,
602
+ config: descriptor.normalize(provider.config)
603
+ };
604
+ }
605
+
606
+ function normalizeNotifications(remoteNotifications = {}) {
607
+ const source = remoteNotifications && typeof remoteNotifications === 'object' && !Array.isArray(remoteNotifications)
608
+ ? remoteNotifications
609
+ : {};
610
+ return {
611
+ providers: Array.isArray(source.providers)
612
+ ? source.providers.map((provider, index) => normalizeProviderWithRegistry(provider, index)).filter(Boolean)
613
+ : []
614
+ };
615
+ }
616
+
617
+ function validateProvider(provider = {}) {
618
+ const normalized = normalizeProviderWithRegistry(provider);
619
+ if (!normalized) throw createValidationError('远程通知渠道类型不正确');
620
+ descriptorFor(normalized.type).validate(normalized.config);
621
+ return normalized;
622
+ }
623
+
624
+ function validateNotifications(remoteNotifications = {}) {
625
+ const normalized = normalizeNotifications(remoteNotifications);
626
+ normalized.providers.forEach(provider => {
627
+ if (provider.enabled === true) validateProvider(provider);
628
+ });
629
+ return normalized;
630
+ }
631
+
632
+ async function sendTest(providerInput = {}) {
633
+ const provider = validateProvider({ ...providerInput, enabled: true });
634
+ return descriptorFor(provider.type).send(provider);
635
+ }
636
+
637
+ return Object.freeze({
638
+ register,
639
+ get(type) {
640
+ return descriptorFor(type);
641
+ },
642
+ list() {
643
+ return [...descriptors.values()].map(publicDescriptor);
644
+ },
645
+ types() {
646
+ return [...descriptors.keys()];
647
+ },
648
+ normalize: normalizeProviderWithRegistry,
649
+ normalizeNotifications,
650
+ validate: validateProvider,
651
+ validateNotifications,
652
+ sendTest
653
+ });
654
+ }
655
+
656
+ let defaultRegistry;
657
+ function getRemoteNotificationProviderRegistry() {
658
+ if (!defaultRegistry) defaultRegistry = createRemoteNotificationProviderRegistry();
659
+ return defaultRegistry;
660
+ }
661
+
662
+ const defaultApi = getRemoteNotificationProviderRegistry();
663
+
664
+ module.exports = {
665
+ REMOTE_PROVIDER_TYPES,
666
+ createRemoteNotificationProviderRegistry,
667
+ getRemoteNotificationProviderRegistry,
668
+ getRemoteProviderTypes: () => defaultApi.list(),
669
+ normalizeRemoteProviderType: normalizeProviderType,
670
+ normalizeRemoteProvider: defaultApi.normalize,
671
+ normalizeRemoteNotificationsConfig: defaultApi.normalizeNotifications,
672
+ validateRemoteProviderConfig: defaultApi.validate,
673
+ validateRemoteNotifications: defaultApi.validateNotifications,
674
+ sendRemoteProviderTest: defaultApi.sendTest,
675
+ validateFeishuWebhookUrl,
676
+ validateHttpsUrl,
677
+ createValidationError
678
+ };