coding-tool-x 3.8.3 → 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 +18 -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 +13 -8
  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
@@ -1,47 +1,59 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
- const http = require('http');
5
- const https = require('https');
6
- const crypto = require('crypto');
7
- const { HttpsProxyAgent } = require('https-proxy-agent');
8
4
  const { execSync, execFileSync } = require('child_process');
9
- const { PATHS, NATIVE_PATHS } = require('../../config/paths');
10
- const { loadUIConfig, saveUIConfig } = require('./ui-config');
11
- const codexSettingsManager = require('./codex-settings-manager');
12
- const geminiSettingsManager = require('./gemini-settings-manager');
13
-
14
- const MANAGED_HOOK_NAME = 'coding-tool-notify';
15
- const MANAGED_OPENCODE_PLUGIN_FILE = 'coding-tool-notify.js';
16
- const MANAGED_OMP_EXTENSION_FILE = 'coding-tool-notify.ts';
17
- const REMOTE_PROVIDER_TYPES = [
18
- 'wechatBot',
19
- 'qqBot',
20
- 'feishuBot',
21
- 'wecomBot',
22
- 'dingtalkBot',
23
- 'telegramBot'
24
- ];
25
-
26
- const REMOTE_PROVIDER_LABELS = {
27
- wechatBot: '微信 Bot',
28
- qqBot: 'QQ Bot',
29
- feishuBot: '飞书 Bot',
30
- wecomBot: '企业微信 Bot',
31
- dingtalkBot: '钉钉 Bot',
32
- telegramBot: 'Telegram Bot'
33
- };
5
+ const { PATHS, NATIVE_PATHS } = require('../config/paths');
6
+ const { loadUIConfig, saveUIConfig } = require('../server/services/ui-config');
7
+ const remoteProviderRegistry = require('./remote-notification-providers');
8
+ const {
9
+ getRemoteProviderTypes,
10
+ normalizeRemoteProvider,
11
+ normalizeRemoteNotificationsConfig,
12
+ validateRemoteProviderConfig,
13
+ validateRemoteNotifications,
14
+ sendRemoteProviderTest,
15
+ validateFeishuWebhookUrl
16
+ } = remoteProviderRegistry;
17
+ const { getPlatformCatalog } = require('../server/services/platform-catalog');
18
+ const notificationHooksDriver = require('./drivers/notification-hooks');
19
+ const {
20
+ buildClaudeCommand,
21
+ buildCodexNotifyCommand,
22
+ buildGeminiCommand,
23
+ buildOpenCodePluginContent,
24
+ buildOmpExtensionContent,
25
+ getOpenCodeManagedPluginPath,
26
+ getOmpManagedExtensionPath,
27
+ getClaudeHookStatus,
28
+ getCodexHookStatus,
29
+ getGeminiHookStatus,
30
+ getOpenCodeHookStatus,
31
+ getOmpHookStatus,
32
+ parseCodexNotificationStatus,
33
+ parseGeminiNotificationStatus,
34
+ parseOpenCodeNotificationStatus,
35
+ parseOmpNotificationStatus
36
+ } = notificationHooksDriver;
37
+
38
+ const MANAGED_HOOK_NAME = notificationHooksDriver.MANAGED_HOOK_NAME;
39
+
40
+ function getConfiguredNativePath(platform, name, fallback) {
41
+ try {
42
+ const { getPlatformContext } = require('../server/platform-context');
43
+ const pathContext = getPlatformContext().registry.resolvePathContext(platform);
44
+ if (pathContext?.customized && pathContext.native?.[name]) {
45
+ return pathContext.native[name];
46
+ }
47
+ } catch (_) {
48
+ // Keep legacy paths when notification helpers are used in isolation.
49
+ }
50
+ return fallback;
51
+ }
34
52
 
35
53
  function normalizeType(type) {
36
54
  return type === 'dialog' || type === 'browser' ? type : 'notification';
37
55
  }
38
56
 
39
- function createClientId(prefix = 'coding-tool') {
40
- const randomPart = typeof crypto.randomUUID === 'function'
41
- ? crypto.randomUUID().replace(/-/g, '').slice(0, 16)
42
- : crypto.randomBytes(8).toString('hex');
43
- return `${prefix}-${randomPart}`;
44
- }
45
57
 
46
58
  function ensureParentDir(filePath) {
47
59
  const dir = path.dirname(filePath);
@@ -69,96 +81,13 @@ function writeJsonFile(filePath, value) {
69
81
  }
70
82
 
71
83
  function readClaudeSettings() {
72
- return readJsonFile(NATIVE_PATHS.claude.settings);
84
+ return readJsonFile(getConfiguredNativePath('claude', 'settings', NATIVE_PATHS.claude.settings));
73
85
  }
74
86
 
75
87
  function writeClaudeSettings(settings) {
76
- writeJsonFile(NATIVE_PATHS.claude.settings, settings);
77
- }
78
-
79
- function trimString(value) {
80
- return String(value || '').trim();
81
- }
82
-
83
- function normalizeRemoteProviderType(type) {
84
- const value = trimString(type);
85
- return REMOTE_PROVIDER_TYPES.includes(value) ? value : '';
86
- }
87
-
88
- function createRemoteProviderId(type, index = 0) {
89
- return `${type || 'provider'}-${Date.now().toString(36)}-${index}`;
90
- }
91
-
92
- function normalizeProviderConfig(type, config = {}) {
93
- const source = config && typeof config === 'object' ? config : {};
94
- switch (type) {
95
- case 'wechatBot':
96
- return {
97
- tokenFile: trimString(source.tokenFile),
98
- botToken: trimString(source.botToken),
99
- targetUserId: trimString(source.targetUserId),
100
- contextToken: trimString(source.contextToken)
101
- };
102
- case 'qqBot':
103
- return {
104
- endpoint: trimString(source.endpoint),
105
- accessToken: trimString(source.accessToken),
106
- targetType: source.targetType === 'group' ? 'group' : 'private',
107
- targetId: trimString(source.targetId)
108
- };
109
- case 'feishuBot':
110
- return {
111
- webhookUrl: trimString(source.webhookUrl)
112
- };
113
- case 'wecomBot':
114
- return {
115
- webhookUrl: trimString(source.webhookUrl)
116
- };
117
- case 'dingtalkBot':
118
- return {
119
- mode: source.mode === 'app' ? 'app' : 'webhook',
120
- webhookUrl: trimString(source.webhookUrl),
121
- clientId: trimString(source.clientId),
122
- clientSecret: trimString(source.clientSecret),
123
- targetType: source.targetType === 'user' ? 'user' : 'group',
124
- targetId: trimString(source.targetId)
125
- };
126
- case 'telegramBot':
127
- return {
128
- botToken: trimString(source.botToken),
129
- chatId: trimString(source.chatId),
130
- proxy: trimString(source.proxy)
131
- };
132
- default:
133
- return {};
134
- }
135
- }
136
-
137
- function normalizeRemoteProvider(provider = {}, index = 0) {
138
- const type = normalizeRemoteProviderType(provider.type);
139
- if (!type) {
140
- return null;
141
- }
142
-
143
- return {
144
- id: trimString(provider.id) || createRemoteProviderId(type, index),
145
- type,
146
- name: trimString(provider.name) || REMOTE_PROVIDER_LABELS[type],
147
- enabled: provider.enabled === true,
148
- config: normalizeProviderConfig(type, provider.config)
149
- };
88
+ writeJsonFile(getConfiguredNativePath('claude', 'settings', NATIVE_PATHS.claude.settings), settings);
150
89
  }
151
90
 
152
- function normalizeRemoteNotificationsConfig(remoteNotifications = {}) {
153
- const source = remoteNotifications && typeof remoteNotifications === 'object' ? remoteNotifications : {};
154
- const providers = Array.isArray(source.providers)
155
- ? source.providers.map((provider, index) => normalizeRemoteProvider(provider, index)).filter(Boolean)
156
- : [];
157
-
158
- return {
159
- providers
160
- };
161
- }
162
91
 
163
92
  function getRemoteNotificationsConfig(uiConfig = loadUIConfig()) {
164
93
  return normalizeRemoteNotificationsConfig(uiConfig.remoteNotifications);
@@ -190,115 +119,7 @@ function createValidationError(message) {
190
119
  return error;
191
120
  }
192
121
 
193
- function validateFeishuWebhookUrl(webhookUrl) {
194
- const value = String(webhookUrl || '').trim();
195
- if (!value) {
196
- return null;
197
- }
198
-
199
- let urlObj;
200
- try {
201
- urlObj = new URL(value);
202
- } catch (error) {
203
- throw createValidationError('飞书 Webhook URL 格式不正确');
204
- }
205
-
206
- if (urlObj.protocol !== 'https:') {
207
- throw createValidationError('飞书 Webhook 必须使用 HTTPS');
208
- }
209
-
210
- if (urlObj.hostname !== 'open.feishu.cn') {
211
- throw createValidationError('仅支持 open.feishu.cn 的飞书 Webhook');
212
- }
213
-
214
- return urlObj;
215
- }
216
-
217
- function validateHttpsUrl(value, label, options = {}) {
218
- const raw = trimString(value);
219
- if (!raw) {
220
- return null;
221
- }
222
-
223
- let urlObj;
224
- try {
225
- urlObj = new URL(raw);
226
- } catch (error) {
227
- throw createValidationError(`${label} 格式不正确`);
228
- }
229
-
230
- if (options.requireHttps !== false && urlObj.protocol !== 'https:') {
231
- throw createValidationError(`${label} 必须使用 HTTPS`);
232
- }
233
-
234
- if (options.hostname && urlObj.hostname !== options.hostname) {
235
- throw createValidationError(`${label} 仅支持 ${options.hostname}`);
236
- }
237
-
238
- return urlObj;
239
- }
240
-
241
- function requireConfigValue(config, key, label) {
242
- if (!trimString(config?.[key])) {
243
- throw createValidationError(`请填写${label}`);
244
- }
245
- }
246
122
 
247
- function validateRemoteProviderConfig(provider = {}) {
248
- const type = normalizeRemoteProviderType(provider.type);
249
- const config = provider.config || {};
250
- if (!type) {
251
- throw createValidationError('远程通知渠道类型不正确');
252
- }
253
-
254
- switch (type) {
255
- case 'wechatBot':
256
- if (!trimString(config.botToken) && !trimString(config.tokenFile)) {
257
- throw createValidationError('请填写微信 Bot Token 或 token.json 路径');
258
- }
259
- requireConfigValue(config, 'targetUserId', '微信接收用户 ID');
260
- break;
261
- case 'qqBot':
262
- requireConfigValue(config, 'endpoint', 'QQ Bot HTTP 地址');
263
- validateHttpsUrl(config.endpoint, 'QQ Bot HTTP 地址', { requireHttps: false });
264
- requireConfigValue(config, 'targetId', 'QQ 接收对象 ID');
265
- break;
266
- case 'feishuBot':
267
- validateFeishuWebhookUrl(config.webhookUrl);
268
- requireConfigValue(config, 'webhookUrl', '飞书 Webhook URL');
269
- break;
270
- case 'wecomBot':
271
- validateHttpsUrl(config.webhookUrl, '企业微信 Webhook URL');
272
- requireConfigValue(config, 'webhookUrl', '企业微信 Webhook URL');
273
- break;
274
- case 'dingtalkBot':
275
- if (config.mode === 'app') {
276
- requireConfigValue(config, 'clientId', '钉钉 App Key');
277
- requireConfigValue(config, 'clientSecret', '钉钉 App Secret');
278
- requireConfigValue(config, 'targetId', '钉钉接收对象 ID');
279
- } else {
280
- validateHttpsUrl(config.webhookUrl, '钉钉 Webhook URL');
281
- requireConfigValue(config, 'webhookUrl', '钉钉 Webhook URL');
282
- }
283
- break;
284
- case 'telegramBot':
285
- requireConfigValue(config, 'botToken', 'Telegram Bot Token');
286
- requireConfigValue(config, 'chatId', 'Telegram Chat ID');
287
- break;
288
- default:
289
- break;
290
- }
291
- }
292
-
293
- function validateRemoteNotifications(remoteNotifications = {}) {
294
- const remote = normalizeRemoteNotificationsConfig(remoteNotifications);
295
- remote.providers.forEach((provider) => {
296
- if (provider.enabled === true) {
297
- validateRemoteProviderConfig(provider);
298
- }
299
- });
300
- return remote;
301
- }
302
123
 
303
124
  function removeNotifyScript() {
304
125
  if (fs.existsSync(PATHS.notifyHook)) {
@@ -333,148 +154,6 @@ function isManagedNotifyPath(input) {
333
154
  return normalizedInput.includes('notify-hook.js') || (normalizedPath && normalizedInput.includes(normalizedPath));
334
155
  }
335
156
 
336
- function buildManagedArgs(source, type, extra = []) {
337
- const mode = normalizeType(type);
338
- return [
339
- `--source=${source}`,
340
- `--mode=${mode}`,
341
- `--cc-notify-type=${mode}`,
342
- ...extra
343
- ];
344
- }
345
-
346
- function quoteShellArg(value) {
347
- const stringValue = String(value || '');
348
- if (/^[A-Za-z0-9_./:=+-]+$/.test(stringValue)) {
349
- return stringValue;
350
- }
351
- return `"${stringValue.replace(/"/g, '\\"')}"`;
352
- }
353
-
354
- function buildClaudeCommand(type) {
355
- const mode = normalizeType(type);
356
- const args = ['node', PATHS.notifyHook, `--source=claude`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
357
- return `${quoteShellArg(args[0])} ${quoteShellArg(args[1])} ${args.slice(2).map(quoteShellArg).join(' ')}`;
358
- }
359
-
360
- function buildCodexNotifyCommand(type) {
361
- const mode = normalizeType(type);
362
- return ['node', PATHS.notifyHook, `--source=codex`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
363
- }
364
-
365
- function buildGeminiCommand(type) {
366
- const mode = normalizeType(type);
367
- const args = ['node', PATHS.notifyHook, `--source=gemini`, `--mode=${mode}`, `--cc-notify-type=${mode}`];
368
- return `${quoteShellArg(args[0])} ${quoteShellArg(args[1])} ${args.slice(2).map(quoteShellArg).join(' ')}`;
369
- }
370
-
371
- function getOpenCodeManagedPluginPath() {
372
- return path.join(NATIVE_PATHS.opencode.config, 'plugins', MANAGED_OPENCODE_PLUGIN_FILE);
373
- }
374
-
375
- function getOmpManagedExtensionPath() {
376
- const ompPaths = NATIVE_PATHS.omp || {};
377
- const extensionsDir = ompPaths.extensions ||
378
- path.join(ompPaths.dir || path.dirname(ompPaths.settings || PATHS.notifyHook), 'extensions');
379
- return path.join(extensionsDir, MANAGED_OMP_EXTENSION_FILE);
380
- }
381
-
382
- function buildOpenCodePluginContent(type) {
383
- const mode = normalizeType(type);
384
- return `// Managed by Coding Tool. Do not edit manually.
385
- // mode:${mode}
386
- import { spawn } from 'node:child_process'
387
-
388
- const SCRIPT_PATH = ${JSON.stringify(PATHS.notifyHook)}
389
- const MODE = ${JSON.stringify(mode)}
390
-
391
- function fire(eventType) {
392
- try {
393
- const child = spawn('node', [
394
- SCRIPT_PATH,
395
- '--source=opencode',
396
- \`--mode=\${MODE}\`,
397
- \`--cc-notify-type=\${MODE}\`,
398
- \`--event-type=\${eventType}\`
399
- ], {
400
- detached: true,
401
- stdio: 'ignore',
402
- windowsHide: true
403
- })
404
- child.unref()
405
- } catch (error) {
406
- // Ignore notification failures.
407
- }
408
- }
409
-
410
- export const CodingToolNotifyPlugin = async () => ({
411
- event: async ({ event }) => {
412
- const eventType = event?.type
413
- if (eventType === 'session.idle' || eventType === 'session.error') {
414
- fire(eventType)
415
- }
416
- }
417
- })
418
- `;
419
- }
420
-
421
- function buildOmpExtensionContent(type) {
422
- const mode = normalizeType(type);
423
- return `// Managed by Coding Tool. Do not edit manually.
424
- // mode:${mode}
425
- import { spawn } from 'node:child_process'
426
-
427
- const SCRIPT_PATH = ${JSON.stringify(PATHS.notifyHook)}
428
- const MODE = ${JSON.stringify(mode)}
429
- let lastFireAt = 0
430
-
431
- function shouldFire() {
432
- const now = Date.now()
433
- if (now - lastFireAt < 1000) {
434
- return false
435
- }
436
- lastFireAt = now
437
- return true
438
- }
439
-
440
- function fire(eventType) {
441
- if (!shouldFire()) {
442
- return
443
- }
444
-
445
- try {
446
- const child = spawn('node', [
447
- SCRIPT_PATH,
448
- '--source=omp',
449
- \`--mode=\${MODE}\`,
450
- \`--cc-notify-type=\${MODE}\`,
451
- \`--event-type=\${eventType}\`
452
- ], {
453
- detached: true,
454
- stdio: 'ignore',
455
- windowsHide: true
456
- })
457
- child.unref()
458
- } catch (error) {
459
- // Ignore notification failures.
460
- }
461
- }
462
-
463
- export default async function CodingToolNotifyExtension(omp) {
464
- const register = (eventName) => {
465
- if (typeof omp?.on !== 'function') {
466
- return
467
- }
468
- omp.on(eventName, async () => {
469
- fire(eventName)
470
- })
471
- }
472
-
473
- register('agent_settled')
474
- register('turn_end')
475
- }
476
- `;
477
- }
478
157
 
479
158
  function escapeForAppleScript(value) {
480
159
  return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
@@ -1382,365 +1061,62 @@ function writeNotifyScript(remoteNotifications = {}) {
1382
1061
  fs.writeFileSync(PATHS.notifyHook, generateNotifyScript(remoteNotifications), { mode: 0o755 });
1383
1062
  }
1384
1063
 
1385
- function getClaudeHookStatus() {
1386
- const settings = readClaudeSettings();
1387
- const stopHooks = Array.isArray(settings?.hooks?.Stop) ? settings.hooks.Stop : [];
1388
- let enabled = false;
1389
- let external = false;
1390
- let type = 'notification';
1391
-
1392
- stopHooks.forEach((group) => {
1393
- const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
1394
- hooks.forEach((hook) => {
1395
- const command = String(hook?.command || '');
1396
- if (!command) return;
1397
-
1398
- const isManaged = isManagedNotifyPath(command);
1399
- if (isManaged) {
1400
- enabled = true;
1401
- type = parseManagedType(command) || type;
1402
- } else {
1403
- external = true;
1404
- }
1405
- });
1406
- });
1407
-
1408
- return {
1409
- enabled,
1410
- external,
1411
- type,
1412
- method: 'Stop Hook'
1413
- };
1414
- }
1415
-
1416
- function saveClaudeHook(enabled, type) {
1417
- const settings = readClaudeSettings();
1418
- const hooks = settings.hooks && typeof settings.hooks === 'object' ? { ...settings.hooks } : {};
1419
- const currentGroups = Array.isArray(hooks.Stop) ? hooks.Stop : [];
1420
-
1421
- const filteredGroups = currentGroups.map((group) => {
1422
- const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
1423
- const nextHooks = groupHooks.filter((hook) => !isManagedNotifyPath(hook?.command));
1424
- if (nextHooks.length === 0) {
1425
- return null;
1426
- }
1427
- return { ...group, hooks: nextHooks };
1428
- }).filter(Boolean);
1429
-
1430
- if (enabled) {
1431
- filteredGroups.push({
1432
- hooks: [
1433
- {
1434
- name: MANAGED_HOOK_NAME,
1435
- type: 'command',
1436
- command: buildClaudeCommand(type)
1437
- }
1438
- ]
1439
- });
1440
- }
1441
-
1442
- if (filteredGroups.length > 0) {
1443
- hooks.Stop = filteredGroups;
1444
- } else {
1445
- delete hooks.Stop;
1446
- }
1447
-
1448
- if (Object.keys(hooks).length > 0) {
1449
- settings.hooks = hooks;
1450
- } else {
1451
- delete settings.hooks;
1452
- }
1453
-
1454
- writeClaudeSettings(settings);
1455
- }
1456
1064
 
1457
- function safeReadCodexConfig() {
1458
- try {
1459
- if (typeof codexSettingsManager.configExists === 'function' && !codexSettingsManager.configExists()) {
1460
- return {};
1461
- }
1462
- return codexSettingsManager.readConfig();
1463
- } catch (error) {
1464
- return {};
1465
- }
1466
- }
1467
-
1468
- function isManagedCodexNotify(notify) {
1469
- return Array.isArray(notify) && notify.some((part) => isManagedNotifyPath(part));
1470
- }
1471
-
1472
- function parseCodexNotificationStatus(config = {}) {
1473
- const notify = Array.isArray(config?.notify) ? config.notify : [];
1474
-
1475
- if (notify.length === 0) {
1476
- return {
1477
- enabled: false,
1478
- external: false,
1479
- type: 'notification',
1480
- method: 'notify'
1481
- };
1482
- }
1483
-
1484
- const joined = notify.join(' ');
1485
- const managed = isManagedCodexNotify(notify);
1486
-
1487
- return {
1488
- enabled: managed,
1489
- external: !managed,
1490
- type: parseManagedType(joined) || 'notification',
1491
- method: 'notify'
1492
- };
1493
- }
1494
-
1495
- function getCodexHookStatus() {
1496
- return parseCodexNotificationStatus(safeReadCodexConfig());
1497
- }
1498
-
1499
- function saveCodexHook(enabled, type) {
1500
- const config = safeReadCodexConfig();
1501
- const nextConfig = (config && typeof config === 'object') ? { ...config } : {};
1502
- const managed = isManagedCodexNotify(nextConfig.notify);
1503
-
1504
- if (enabled) {
1505
- nextConfig.notify = buildCodexNotifyCommand(type);
1506
- } else if (managed) {
1507
- delete nextConfig.notify;
1508
- }
1509
-
1510
- ensureParentDir(NATIVE_PATHS.codex.config);
1511
- codexSettingsManager.writeConfig(nextConfig);
1512
- }
1513
-
1514
- function safeReadGeminiSettings() {
1515
- try {
1516
- if (typeof geminiSettingsManager.settingsExists === 'function' && !geminiSettingsManager.settingsExists()) {
1517
- return {};
1065
+ function getHookPlatformEntries(catalog = getPlatformCatalog()) {
1066
+ const manifests = typeof catalog?.list === 'function'
1067
+ ? catalog.list({ capability: 'hooks' })
1068
+ : [];
1069
+ const entries = [];
1070
+ for (const manifest of manifests) {
1071
+ const driver = typeof catalog.driver === 'function'
1072
+ ? catalog.driver(manifest.key, 'hooks')
1073
+ : null;
1074
+ if (!driver
1075
+ || typeof driver.getHooks !== 'function'
1076
+ || typeof driver.saveHooks !== 'function'
1077
+ || typeof driver.testHooks !== 'function'
1078
+ || typeof driver.getDefinition !== 'function') {
1079
+ continue;
1518
1080
  }
1519
- return geminiSettingsManager.readSettings();
1520
- } catch (error) {
1521
- return {};
1081
+ const definition = driver.getDefinition();
1082
+ if (!definition || typeof definition !== 'object') continue;
1083
+ entries.push({ manifest, driver, definition });
1522
1084
  }
1085
+ return entries;
1523
1086
  }
1524
1087
 
1525
- function isManagedGeminiHook(hook) {
1526
- const command = String(hook?.command || '');
1527
- const name = String(hook?.name || '');
1528
- return name === MANAGED_HOOK_NAME || isManagedNotifyPath(command);
1529
- }
1530
-
1531
- function parseGeminiNotificationStatus(settings = {}) {
1532
- const afterAgentGroups = Array.isArray(settings?.hooks?.AfterAgent) ? settings.hooks.AfterAgent : [];
1533
- let enabled = false;
1534
- let external = false;
1535
- let type = 'notification';
1536
-
1537
- afterAgentGroups.forEach((group) => {
1538
- const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
1539
- hooks.forEach((hook) => {
1540
- if (isManagedGeminiHook(hook)) {
1541
- enabled = true;
1542
- type = parseManagedType(hook.command) || type;
1543
- } else {
1544
- external = true;
1545
- }
1546
- });
1547
- });
1548
-
1088
+ function toPublicHookDefinition({ manifest, definition }) {
1549
1089
  return {
1550
- enabled,
1551
- external,
1552
- type,
1553
- method: 'AfterAgent Hook'
1090
+ key: manifest.key,
1091
+ label: manifest.label || definition.label || manifest.key,
1092
+ description: typeof definition.description === 'string' ? definition.description : '',
1093
+ implementation: typeof definition.implementation === 'string' ? definition.implementation : '',
1094
+ externalMessage: typeof definition.externalMessage === 'string' ? definition.externalMessage : '',
1095
+ hints: Array.isArray(definition.hints) ? [...definition.hints] : []
1554
1096
  };
1555
1097
  }
1556
1098
 
1557
- function getGeminiHookStatus() {
1558
- return parseGeminiNotificationStatus(safeReadGeminiSettings());
1559
- }
1560
-
1561
- function saveGeminiHook(enabled, type) {
1562
- const settings = safeReadGeminiSettings();
1563
- const nextSettings = (settings && typeof settings === 'object') ? { ...settings } : {};
1564
- const hooks = nextSettings.hooks && typeof nextSettings.hooks === 'object' ? { ...nextSettings.hooks } : {};
1565
- const currentGroups = Array.isArray(hooks.AfterAgent) ? hooks.AfterAgent : [];
1566
-
1567
- const filteredGroups = currentGroups.map((group) => {
1568
- const groupHooks = Array.isArray(group?.hooks) ? group.hooks : [];
1569
- const nextHooks = groupHooks.filter((hook) => !isManagedGeminiHook(hook));
1570
- if (nextHooks.length === 0) {
1571
- return null;
1572
- }
1573
- return { ...group, hooks: nextHooks };
1574
- }).filter(Boolean);
1575
-
1576
- if (enabled) {
1577
- filteredGroups.push({
1578
- matcher: '*',
1579
- hooks: [
1580
- {
1581
- name: MANAGED_HOOK_NAME,
1582
- type: 'command',
1583
- command: buildGeminiCommand(type)
1584
- }
1585
- ]
1586
- });
1587
- }
1588
-
1589
- if (filteredGroups.length > 0) {
1590
- hooks.AfterAgent = filteredGroups;
1591
- } else {
1592
- delete hooks.AfterAgent;
1593
- }
1594
-
1595
- if (Object.keys(hooks).length > 0) {
1596
- nextSettings.hooks = hooks;
1597
- } else {
1598
- delete nextSettings.hooks;
1599
- }
1600
-
1601
- ensureParentDir(geminiSettingsManager.getSettingsPath());
1602
- geminiSettingsManager.writeSettings(nextSettings);
1603
- }
1604
-
1605
- function parseOpenCodeNotificationStatus(content = '') {
1606
- if (!content) {
1607
- return {
1608
- enabled: false,
1609
- external: false,
1610
- type: 'notification',
1611
- method: 'Plugin Events'
1612
- };
1613
- }
1614
-
1615
- return {
1616
- enabled: true,
1617
- external: false,
1618
- type: parseManagedType(content) || 'notification',
1619
- method: 'Plugin Events'
1620
- };
1621
- }
1622
-
1623
- function parseOmpNotificationStatus(content = '') {
1624
- if (!content) {
1625
- return {
1626
- enabled: false,
1627
- external: false,
1628
- type: 'notification',
1629
- method: 'Extension Events'
1630
- };
1631
- }
1632
-
1633
- return {
1634
- enabled: true,
1635
- external: false,
1636
- type: parseManagedType(content) || 'notification',
1637
- method: 'Extension Events'
1638
- };
1639
- }
1640
-
1641
- function getOpenCodeHookStatus() {
1642
- const pluginPath = getOpenCodeManagedPluginPath();
1643
- if (!fs.existsSync(pluginPath)) {
1644
- return parseOpenCodeNotificationStatus('');
1645
- }
1646
-
1647
- return parseOpenCodeNotificationStatus(fs.readFileSync(pluginPath, 'utf8'));
1648
- }
1649
-
1650
- function getOmpHookStatus() {
1651
- const extensionPath = getOmpManagedExtensionPath();
1652
- if (!fs.existsSync(extensionPath)) {
1653
- return parseOmpNotificationStatus('');
1654
- }
1655
-
1656
- return parseOmpNotificationStatus(fs.readFileSync(extensionPath, 'utf8'));
1657
- }
1658
-
1659
- function saveOpenCodeHook(enabled, type) {
1660
- const pluginPath = getOpenCodeManagedPluginPath();
1661
- const opencodeSettingsManager = require('./opencode-settings-manager');
1662
- const configPath = opencodeSettingsManager.selectConfigPath();
1663
-
1664
- if (!enabled) {
1665
- // Remove plugin file
1666
- if (fs.existsSync(pluginPath)) {
1667
- fs.unlinkSync(pluginPath);
1668
- }
1669
-
1670
- // Remove from opencode.json plugins array
1671
- if (fs.existsSync(configPath)) {
1672
- try {
1673
- const config = opencodeSettingsManager.readConfig(configPath);
1674
- if (Array.isArray(config.plugins)) {
1675
- config.plugins = config.plugins.filter(p => p !== './plugins/coding-tool-notify.js');
1676
- if (config.plugins.length === 0) {
1677
- delete config.plugins;
1678
- }
1679
- opencodeSettingsManager.writeConfig(configPath, config);
1680
- }
1681
- } catch (error) {
1682
- console.error('Failed to update opencode.json:', error);
1683
- }
1684
- }
1685
- return;
1686
- }
1687
-
1688
- // Create plugin file
1689
- ensureParentDir(pluginPath);
1690
- fs.writeFileSync(pluginPath, buildOpenCodePluginContent(type), 'utf8');
1691
-
1692
- // Add to opencode.json plugins array
1693
- try {
1694
- const config = fs.existsSync(configPath)
1695
- ? opencodeSettingsManager.readConfig(configPath)
1696
- : {};
1697
-
1698
- if (!Array.isArray(config.plugins)) {
1699
- config.plugins = [];
1700
- }
1701
-
1702
- const pluginRef = './plugins/coding-tool-notify.js';
1703
- if (!config.plugins.includes(pluginRef)) {
1704
- config.plugins.push(pluginRef);
1705
- }
1706
-
1707
- opencodeSettingsManager.writeConfig(configPath, config);
1708
- } catch (error) {
1709
- console.error('Failed to update opencode.json:', error);
1710
- }
1711
- }
1712
-
1713
- function saveOmpHook(enabled, type) {
1714
- const extensionPath = getOmpManagedExtensionPath();
1715
-
1716
- if (!enabled) {
1717
- if (fs.existsSync(extensionPath)) {
1718
- fs.unlinkSync(extensionPath);
1719
- }
1720
- return;
1721
- }
1722
-
1723
- ensureParentDir(extensionPath);
1724
- fs.writeFileSync(extensionPath, buildOmpExtensionContent(type), 'utf8');
1725
- }
1726
-
1727
- function getNotificationSettings() {
1099
+ function getNotificationSettings({ catalog } = {}) {
1100
+ const entries = getHookPlatformEntries(catalog);
1728
1101
  const uiConfig = loadUIConfig();
1729
1102
  const remoteNotifications = getRemoteNotificationsConfig(uiConfig);
1103
+ const platforms = {};
1104
+ const platformDefinitions = [];
1105
+ for (const entry of entries) {
1106
+ platforms[entry.manifest.key] = entry.driver.getHooks();
1107
+ platformDefinitions.push(toPublicHookDefinition(entry));
1108
+ }
1109
+ const claudeStatus = platforms.claude || { enabled: false, type: 'notification' };
1730
1110
  return {
1731
1111
  success: true,
1732
1112
  platform: os.platform(),
1733
1113
  remoteNotifications,
1734
- remoteProviderTypes: REMOTE_PROVIDER_TYPES.map((type) => ({
1735
- type,
1736
- label: REMOTE_PROVIDER_LABELS[type]
1737
- })),
1738
- platforms: {
1739
- claude: getClaudeHookStatus(),
1740
- codex: getCodexHookStatus(),
1741
- gemini: getGeminiHookStatus(),
1742
- opencode: getOpenCodeHookStatus(),
1743
- omp: getOmpHookStatus()
1114
+ remoteProviderTypes: getRemoteProviderTypes(),
1115
+ platformDefinitions,
1116
+ platforms,
1117
+ stopHook: {
1118
+ enabled: claudeStatus.enabled === true,
1119
+ type: normalizeType(claudeStatus.type)
1744
1120
  }
1745
1121
  };
1746
1122
  }
@@ -1800,43 +1176,44 @@ function emitBrowserNotification(input = {}) {
1800
1176
  timestamp: Date.now()
1801
1177
  };
1802
1178
 
1803
- const { broadcastBrowserNotification } = require('../websocket-server');
1179
+ const { broadcastBrowserNotification } = require('../server/websocket-server');
1804
1180
  broadcastBrowserNotification(payload);
1805
1181
  return payload;
1806
1182
  }
1807
-
1808
- function saveNotificationSettings(input = {}) {
1183
+ function saveNotificationSettings(input = {}, { catalog } = {}) {
1809
1184
  const remoteNotifications = validateRemoteNotifications(
1810
1185
  input.remoteNotifications !== undefined
1811
1186
  ? input.remoteNotifications
1812
1187
  : {}
1813
1188
  );
1814
- const platforms = {
1815
- claude: normalizePlatformInput(input?.platforms?.claude),
1816
- codex: normalizePlatformInput(input?.platforms?.codex),
1817
- gemini: normalizePlatformInput(input?.platforms?.gemini),
1818
- opencode: normalizePlatformInput(input?.platforms?.opencode),
1819
- omp: normalizePlatformInput(input?.platforms?.omp)
1820
- };
1189
+ const entries = getHookPlatformEntries(catalog);
1190
+ const inputPlatforms = input.platforms && typeof input.platforms === 'object' && !Array.isArray(input.platforms)
1191
+ ? input.platforms
1192
+ : {};
1193
+ const platforms = Object.fromEntries(entries.map(entry => {
1194
+ const key = entry.manifest.key;
1195
+ const platformInput = Object.prototype.hasOwnProperty.call(inputPlatforms, key)
1196
+ ? inputPlatforms[key]
1197
+ : Object.prototype.hasOwnProperty.call(input, key)
1198
+ ? input[key]
1199
+ : key === 'claude'
1200
+ ? input.stopHook
1201
+ : undefined;
1202
+ return [key, normalizePlatformInput(platformInput)];
1203
+ }));
1821
1204
 
1822
- saveNotificationUiConfig(remoteNotifications, platforms.claude.enabled);
1205
+ const claudeEntry = entries.find(entry => entry.manifest.key === 'claude');
1206
+ saveNotificationUiConfig(remoteNotifications, claudeEntry ? platforms.claude.enabled : undefined);
1823
1207
 
1824
- const hasManagedPlatform = Object.values(platforms).some((platform) => platform.enabled);
1825
- if (hasManagedPlatform) {
1826
- writeNotifyScript(remoteNotifications);
1208
+ const hasManagedPlatform = Object.values(platforms).some(platform => platform.enabled);
1209
+ for (const entry of entries) {
1210
+ entry.driver.saveHooks(platforms[entry.manifest.key]);
1827
1211
  }
1828
1212
 
1829
- saveClaudeHook(platforms.claude.enabled, platforms.claude.type);
1830
- saveCodexHook(platforms.codex.enabled, platforms.codex.type);
1831
- saveGeminiHook(platforms.gemini.enabled, platforms.gemini.type);
1832
- saveOpenCodeHook(platforms.opencode.enabled, platforms.opencode.type);
1833
- saveOmpHook(platforms.omp.enabled, platforms.omp.type);
1834
-
1835
- if (!hasManagedPlatform) {
1836
- removeNotifyScript();
1837
- }
1213
+ if (hasManagedPlatform) writeNotifyScript(remoteNotifications);
1214
+ else removeNotifyScript();
1838
1215
 
1839
- return getNotificationSettings();
1216
+ return getNotificationSettings({ catalog });
1840
1217
  }
1841
1218
 
1842
1219
  function parseNotifyTypeMarker(command) {
@@ -1950,20 +1327,19 @@ function normalizeSavedPlatformStatus(platform = {}) {
1950
1327
  }
1951
1328
 
1952
1329
  function buildLegacyClaudeSaveInput(input = {}, currentSettings = getNotificationSettings()) {
1330
+ const platforms = {
1331
+ ...(currentSettings?.platforms && typeof currentSettings.platforms === 'object'
1332
+ ? currentSettings.platforms
1333
+ : {})
1334
+ };
1335
+ platforms.claude = input.stopHook !== undefined
1336
+ ? normalizePlatformInput(input.stopHook)
1337
+ : { enabled: false, type: 'notification' };
1953
1338
  return {
1954
- platforms: {
1955
- claude: input.stopHook !== undefined
1956
- ? normalizePlatformInput(input.stopHook)
1957
- : { enabled: false, type: 'notification' },
1958
- codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
1959
- gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
1960
- opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
1961
- omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
1962
- },
1339
+ platforms,
1963
1340
  remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
1964
1341
  };
1965
1342
  }
1966
-
1967
1343
  function getLegacyClaudeHookSettings() {
1968
1344
  return {
1969
1345
  success: true,
@@ -1986,21 +1362,21 @@ function initDefaultHooks() {
1986
1362
  return;
1987
1363
  }
1988
1364
 
1365
+ const entries = getHookPlatformEntries();
1366
+ const claudeEntry = entries.find(entry => entry.manifest.key === 'claude');
1367
+ if (!claudeEntry) return;
1368
+
1989
1369
  const currentClaudeSettings = readClaudeSettings();
1990
1370
  const currentStatus = parseStopHookStatus(currentClaudeSettings);
1371
+ const currentSettings = getNotificationSettings();
1372
+ const platforms = { ...currentSettings.platforms };
1991
1373
 
1992
1374
  if (currentStatus.enabled) {
1993
1375
  if (shouldRepairStopHook(currentClaudeSettings)) {
1994
- const currentSettings = getNotificationSettings();
1376
+ platforms.claude = { enabled: true, type: currentStatus.type || 'notification' };
1995
1377
  saveNotificationSettings({
1996
- platforms: {
1997
- claude: { enabled: true, type: currentStatus.type || 'notification' },
1998
- codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
1999
- gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
2000
- opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
2001
- omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
2002
- },
2003
- remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
1378
+ platforms,
1379
+ remoteNotifications: currentSettings.remoteNotifications || { providers: [] }
2004
1380
  });
2005
1381
  console.log('[Claude Hooks] 检测到旧版 Stop hook 路径,已自动修复');
2006
1382
  } else {
@@ -2009,16 +1385,10 @@ function initDefaultHooks() {
2009
1385
  return;
2010
1386
  }
2011
1387
 
2012
- const currentSettings = getNotificationSettings();
1388
+ platforms.claude = { enabled: true, type: 'notification' };
2013
1389
  saveNotificationSettings({
2014
- platforms: {
2015
- claude: { enabled: true, type: 'notification' },
2016
- codex: normalizeSavedPlatformStatus(currentSettings?.platforms?.codex),
2017
- gemini: normalizeSavedPlatformStatus(currentSettings?.platforms?.gemini),
2018
- opencode: normalizeSavedPlatformStatus(currentSettings?.platforms?.opencode),
2019
- omp: normalizeSavedPlatformStatus(currentSettings?.platforms?.omp)
2020
- },
2021
- remoteNotifications: currentSettings?.remoteNotifications || { providers: [] }
1390
+ platforms,
1391
+ remoteNotifications: currentSettings.remoteNotifications || { providers: [] }
2022
1392
  });
2023
1393
  console.log('[Claude Hooks] 已自动开启任务完成通知(右上角卡片)');
2024
1394
  } catch (error) {
@@ -2026,261 +1396,6 @@ function initDefaultHooks() {
2026
1396
  }
2027
1397
  }
2028
1398
 
2029
- function sendFeishuTest(webhookUrl) {
2030
- return new Promise((resolve, reject) => {
2031
- try {
2032
- const urlObj = validateFeishuWebhookUrl(webhookUrl);
2033
- const data = JSON.stringify({
2034
- msg_type: 'interactive',
2035
- card: {
2036
- header: {
2037
- title: { tag: 'plain_text', content: 'Coding Tool - 测试通知' },
2038
- template: 'blue'
2039
- },
2040
- elements: [
2041
- {
2042
- tag: 'div',
2043
- text: { tag: 'lark_md', content: '**状态**: 这是一条测试通知' }
2044
- },
2045
- {
2046
- tag: 'div',
2047
- text: { tag: 'lark_md', content: '**时间**: ' + new Date().toLocaleString('zh-CN') }
2048
- }
2049
- ]
2050
- }
2051
- });
2052
-
2053
- const options = {
2054
- hostname: urlObj.hostname,
2055
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
2056
- path: urlObj.pathname + urlObj.search,
2057
- method: 'POST',
2058
- headers: {
2059
- 'Content-Type': 'application/json',
2060
- 'Content-Length': Buffer.byteLength(data)
2061
- },
2062
- timeout: 10000
2063
- };
2064
-
2065
- const requestModule = urlObj.protocol === 'https:' ? https : http;
2066
- const request = requestModule.request(options, () => resolve());
2067
- request.on('error', reject);
2068
- request.on('timeout', () => {
2069
- request.destroy(new Error('飞书测试通知超时'));
2070
- });
2071
- request.write(data);
2072
- request.end();
2073
- } catch (error) {
2074
- reject(error);
2075
- }
2076
- });
2077
- }
2078
-
2079
- function requestJson(url, data, headers = {}, extraOptions = {}) {
2080
- return new Promise((resolve, reject) => {
2081
- try {
2082
- const urlObj = new URL(url);
2083
- const body = JSON.stringify(data);
2084
- const requestModule = urlObj.protocol === 'https:' ? https : http;
2085
- const request = requestModule.request({
2086
- hostname: urlObj.hostname,
2087
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
2088
- path: urlObj.pathname + urlObj.search,
2089
- method: 'POST',
2090
- timeout: 10000,
2091
- headers: {
2092
- 'Content-Type': 'application/json',
2093
- 'Content-Length': Buffer.byteLength(body),
2094
- ...headers
2095
- },
2096
- ...extraOptions
2097
- }, (response) => {
2098
- response.resume();
2099
- if (response.statusCode >= 200 && response.statusCode < 300) {
2100
- resolve();
2101
- } else {
2102
- reject(new Error(`远程通知返回 HTTP ${response.statusCode}`));
2103
- }
2104
- });
2105
- request.on('error', reject);
2106
- request.on('timeout', () => {
2107
- request.destroy(new Error('远程通知测试超时'));
2108
- });
2109
- request.write(body);
2110
- request.end();
2111
- } catch (error) {
2112
- reject(error);
2113
- }
2114
- });
2115
- }
2116
-
2117
- function requestDingTalkAccessToken(config = {}) {
2118
- if (!config.clientId || !config.clientSecret) {
2119
- return Promise.resolve('');
2120
- }
2121
- return new Promise((resolve, reject) => {
2122
- try {
2123
- const body = JSON.stringify({ appKey: config.clientId, appSecret: config.clientSecret });
2124
- const request = https.request({
2125
- hostname: 'api.dingtalk.com',
2126
- path: '/v1.0/oauth2/accessToken',
2127
- method: 'POST',
2128
- timeout: 10000,
2129
- headers: {
2130
- 'Content-Type': 'application/json',
2131
- 'Content-Length': Buffer.byteLength(body)
2132
- }
2133
- }, (response) => {
2134
- let responseBody = '';
2135
- response.setEncoding('utf8');
2136
- response.on('data', (chunk) => { responseBody += chunk; });
2137
- response.on('end', () => {
2138
- try {
2139
- const parsed = JSON.parse(responseBody);
2140
- if (response.statusCode >= 200 && response.statusCode < 300 && parsed.accessToken) {
2141
- resolve(parsed.accessToken);
2142
- return;
2143
- }
2144
- reject(new Error(`钉钉 Access Token 获取失败: ${responseBody.slice(0, 300)}`));
2145
- } catch (error) {
2146
- reject(error);
2147
- }
2148
- });
2149
- });
2150
- request.on('error', reject);
2151
- request.on('timeout', () => {
2152
- request.destroy(new Error('钉钉 Access Token 获取超时'));
2153
- });
2154
- request.write(body);
2155
- request.end();
2156
- } catch (error) {
2157
- reject(error);
2158
- }
2159
- });
2160
- }
2161
-
2162
- function buildTestNotificationContext() {
2163
- return {
2164
- title: 'Coding Tool - 测试通知',
2165
- message: '这是一条测试通知',
2166
- source: 'test',
2167
- eventType: 'test',
2168
- timestamp: new Date().toLocaleString('zh-CN'),
2169
- hostname: os.hostname()
2170
- };
2171
- }
2172
-
2173
- function formatProviderText(ctx) {
2174
- return [
2175
- ctx.title,
2176
- `来源: ${ctx.source}`,
2177
- `状态: ${ctx.message}`,
2178
- `时间: ${ctx.timestamp}`,
2179
- `设备: ${ctx.hostname}`
2180
- ].join('\n');
2181
- }
2182
-
2183
- async function sendRemoteProviderTest(providerInput = {}) {
2184
- const provider = normalizeRemoteProvider(providerInput);
2185
- if (!provider) {
2186
- throw createValidationError('远程通知渠道类型不正确');
2187
- }
2188
- validateRemoteProviderConfig({ ...provider, enabled: true });
2189
-
2190
- const config = provider.config || {};
2191
- const ctx = buildTestNotificationContext();
2192
- switch (provider.type) {
2193
- case 'feishuBot':
2194
- return sendFeishuTest(config.webhookUrl);
2195
- case 'qqBot': {
2196
- const pathSuffix = config.targetType === 'group' ? '/send_group_msg' : '/send_private_msg';
2197
- return requestJson(String(config.endpoint).replace(/\/$/, '') + pathSuffix, {
2198
- [config.targetType === 'group' ? 'group_id' : 'user_id']: config.targetId,
2199
- message: formatProviderText(ctx)
2200
- }, config.accessToken ? { Authorization: `Bearer ${config.accessToken}` } : {});
2201
- }
2202
- case 'wecomBot':
2203
- return requestJson(config.webhookUrl, {
2204
- msgtype: 'markdown',
2205
- markdown: { content: `**${ctx.title}**\n> 状态: ${ctx.message}\n> 时间: ${ctx.timestamp}` }
2206
- });
2207
- case 'dingtalkBot':
2208
- if (config.mode === 'app') {
2209
- return sendDingTalkAppProviderTest(config, ctx);
2210
- }
2211
- return requestJson(config.webhookUrl, {
2212
- msgtype: 'markdown',
2213
- markdown: { title: ctx.title, text: `### ${ctx.title}\n\n- 状态: ${ctx.message}\n- 时间: ${ctx.timestamp}` }
2214
- });
2215
- case 'telegramBot':
2216
- return requestTelegramMessage(config, ctx);
2217
- case 'wechatBot':
2218
- if (!config.botToken && config.tokenFile) {
2219
- try {
2220
- const raw = fs.readFileSync(config.tokenFile, 'utf8');
2221
- const parsed = JSON.parse(raw);
2222
- config.botToken = parsed.bot_token || parsed.token || '';
2223
- } catch (error) {
2224
- throw createValidationError('无法读取微信 token.json');
2225
- }
2226
- }
2227
- requireConfigValue(config, 'botToken', '微信 Bot Token');
2228
- return requestJson('https://ilinkai.weixin.qq.com/ilink/bot/sendmessage', {
2229
- msg: {
2230
- from_user_id: '',
2231
- to_user_id: config.targetUserId,
2232
- client_id: createClientId('coding-tool'),
2233
- message_type: 2,
2234
- message_state: 2,
2235
- item_list: [{ type: 1, text_item: { text: formatProviderText(ctx) } }],
2236
- ...(config.contextToken ? { context_token: config.contextToken } : {})
2237
- },
2238
- base_info: { channel_version: '2.1.8' }
2239
- }, {
2240
- AuthorizationType: 'ilink_bot_token',
2241
- Authorization: `Bearer ${config.botToken}`,
2242
- 'X-WECHAT-UIN': Buffer.from(String(Math.floor(Math.random() * 0xffffffff))).toString('base64')
2243
- });
2244
- default:
2245
- throw createValidationError('不支持的远程通知渠道');
2246
- }
2247
- }
2248
-
2249
- function requestTelegramMessage(config = {}, ctx) {
2250
- const url = `https://api.telegram.org/bot${config.botToken}/sendMessage`;
2251
- const payload = {
2252
- chat_id: config.chatId,
2253
- text: formatProviderText(ctx)
2254
- };
2255
- const agent = config.proxy ? new HttpsProxyAgent(config.proxy) : null;
2256
- return requestJson(url, payload, {}, agent ? { agent } : {});
2257
- }
2258
-
2259
- async function sendDingTalkAppProviderTest(config = {}, ctx) {
2260
- const token = await requestDingTalkAccessToken(config);
2261
- if (!token) {
2262
- throw createValidationError('钉钉 App Access Token 获取失败');
2263
- }
2264
- const isGroup = config.targetType !== 'user';
2265
- const payload = {
2266
- robotCode: config.clientId,
2267
- msgKey: 'sampleMarkdown',
2268
- msgParam: JSON.stringify({
2269
- title: ctx.title,
2270
- text: `### ${ctx.title}\n\n- 状态: ${ctx.message}\n- 时间: ${ctx.timestamp}\n- 设备: ${ctx.hostname}`
2271
- })
2272
- };
2273
- if (isGroup) {
2274
- payload.openConversationId = config.targetId;
2275
- } else {
2276
- payload.userIds = [config.targetId];
2277
- }
2278
- return requestJson(
2279
- 'https://api.dingtalk.com/v1.0/robot/' + (isGroup ? 'groupMessages/send' : 'oToMessages/batchSend'),
2280
- payload,
2281
- { 'x-acs-dingtalk-access-token': token }
2282
- );
2283
- }
2284
1399
 
2285
1400
  function generateSystemNotificationCommand(type, message, platformOverride = os.platform()) {
2286
1401
  const normalizedType = normalizeType(type);
@@ -2375,14 +1490,14 @@ function syncManagedNotificationAssets() {
2375
1490
  return settings;
2376
1491
  }
2377
1492
 
2378
- function testNotification({ type, provider } = {}) {
1493
+ function testNotification({ type, provider, source = 'claude' } = {}) {
2379
1494
  if (provider) {
2380
1495
  return sendRemoteProviderTest(provider);
2381
1496
  }
2382
1497
 
2383
1498
  if (normalizeType(type) === 'browser') {
2384
1499
  emitBrowserNotification({
2385
- source: 'claude',
1500
+ source,
2386
1501
  title: 'coding-tool-x',
2387
1502
  message: '这是一条浏览器测试通知'
2388
1503
  });
@@ -2412,6 +1527,8 @@ module.exports = {
2412
1527
  normalizeRemoteNotificationsConfig,
2413
1528
  validateRemoteProviderConfig,
2414
1529
  _test: {
1530
+ getHookPlatformEntries,
1531
+ toPublicHookDefinition,
2415
1532
  applyClaudeDisablePreference,
2416
1533
  getManagedCommandType,
2417
1534
  parseManagedType,