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
@@ -1,53 +1,30 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
- const { convertSession, previewConversion } = require('../services/session-converter');
4
- const {
5
- SUPPORTED_SOURCE_TYPES,
6
- SUPPORTED_TARGET_APIS,
7
- convertToOpenCodePayload,
8
- convertClaudeToOpenCodePayload,
9
- convertCodexToOpenCodePayload,
10
- convertGeminiToOpenCodePayload,
11
- normalizeSourceType
12
- } = require('../services/opencode-gateway-converter');
3
+ const { getPlatformCatalog } = require('../services/platform-catalog');
13
4
 
14
5
  /**
15
6
  * 获取支持的格式列表
16
7
  * GET /api/convert/formats
17
8
  */
18
9
  router.get('/formats', (req, res) => {
10
+ const driver = getPlatformCatalog().driver('opencode', 'conversion');
11
+ const formats = driver?.formats?.() || driver?.getFormats?.();
12
+ if (!formats) {
13
+ return res.status(404).json({ success: false, error: 'Conversion is not supported' });
14
+ }
15
+ const sourceTypes = formats.sourceTypes || [];
19
16
  res.json({
20
- formats: [
21
- {
22
- id: 'claude',
23
- name: 'Claude Code',
24
- description: 'Anthropic Claude Code CLI session format',
25
- extension: '.jsonl',
26
- icon: 'claude'
27
- },
28
- {
29
- id: 'codex',
30
- name: 'OpenAI Codex',
31
- description: 'OpenAI Codex CLI session format',
32
- extension: '.jsonl',
33
- icon: 'codex'
34
- },
35
- {
36
- id: 'gemini',
37
- name: 'Google Gemini',
38
- description: 'Google Gemini CLI session format',
39
- extension: '.json',
40
- icon: 'gemini'
41
- }
42
- ],
43
- conversions: [
44
- { from: 'claude', to: 'codex' },
45
- { from: 'claude', to: 'gemini' },
46
- { from: 'codex', to: 'claude' },
47
- { from: 'codex', to: 'gemini' },
48
- { from: 'gemini', to: 'claude' },
49
- { from: 'gemini', to: 'codex' }
50
- ]
17
+ formats: sourceTypes.map(type => ({
18
+ id: type,
19
+ name: (formats.formats || []).find(item => item.id === type)?.name
20
+ || String(type).replace(/[-_]/g, ' ').replace(/\b\w/g, char => char.toUpperCase()),
21
+ description: `${type} session format`,
22
+ extension: '.jsonl',
23
+ icon: type
24
+ })),
25
+ conversions: sourceTypes.flatMap(sourceType => sourceTypes
26
+ .filter(targetType => targetType !== sourceType)
27
+ .map(targetType => ({ from: sourceType, to: targetType })))
51
28
  });
52
29
  });
53
30
 
@@ -56,42 +33,47 @@ router.get('/formats', (req, res) => {
56
33
  * GET /api/convert/opencode/formats
57
34
  */
58
35
  router.get('/opencode/formats', (req, res) => {
36
+ const driver = getConversionDriver();
37
+ const formats = driver?.formats?.() || driver?.getFormats?.();
38
+ if (!formats) {
39
+ return res.status(404).json({ success: false, error: 'Conversion is not supported' });
40
+ }
41
+ const sourceTypes = formats.sourceTypes || [];
59
42
  res.json({
60
- sourceTypes: SUPPORTED_SOURCE_TYPES.map(type => ({
43
+ sourceTypes: sourceTypes.map(type => ({
61
44
  id: type,
62
- name: type === 'claude'
63
- ? 'Claude Code'
64
- : type === 'codex'
65
- ? 'Codex'
66
- : 'Gemini'
45
+ name: (formats.formats || []).find(item => item.id === type)?.name
46
+ || String(type).replace(/[-_]/g, ' ').replace(/\b\w/g, char => char.toUpperCase())
67
47
  })),
68
48
  target: 'opencode',
69
- targetApis: SUPPORTED_TARGET_APIS,
70
- defaultTargetApi: 'responses',
71
- endpoints: {
72
- responses: '/v1/responses',
73
- 'chat.completions': '/v1/chat/completions'
74
- },
75
- sourceEndpoints: {
76
- claude: '/api/convert/opencode/claude',
77
- codex: '/api/convert/opencode/codex',
78
- gemini: '/api/convert/opencode/gemini'
79
- }
49
+ targetApis: formats.targetApis || [],
50
+ defaultTargetApi: formats.defaultTargetApi,
51
+ endpoints: formats.endpoints || {},
52
+ sourceEndpoints: Object.fromEntries(sourceTypes.map(type => [type, `/api/convert/opencode/${type}`]))
80
53
  });
81
54
  });
82
55
 
83
- function handleOpenCodeConvert(req, res, sourceType, converter) {
56
+ function getConversionDriver() {
57
+ return getPlatformCatalog().driver('opencode', 'conversion');
58
+ }
59
+
60
+ function handleOpenCodeConvert(req, res, sourceType) {
84
61
  try {
85
62
  const { payload, options = {} } = req.body || {};
63
+ const driver = getConversionDriver();
86
64
 
65
+ if (!driver) {
66
+ return res.status(404).json({ success: false, error: 'Conversion is not supported' });
67
+ }
87
68
  if (!payload) {
88
69
  return res.status(400).json({
89
70
  success: false,
90
71
  error: 'Missing required parameters: payload'
91
72
  });
92
73
  }
93
-
94
- const result = converter({ payload, options });
74
+ const result = driver.convertSource
75
+ ? driver.convertSource(sourceType, payload, options)
76
+ : driver.convert({ sourceType, payload, options });
95
77
  return res.json({
96
78
  success: true,
97
79
  ...result
@@ -105,31 +87,8 @@ function handleOpenCodeConvert(req, res, sourceType, converter) {
105
87
  }
106
88
  }
107
89
 
108
- /**
109
- * Claude Code -> OpenCode
110
- * POST /api/convert/opencode/claude
111
- * Body: { payload, options? }
112
- */
113
- router.post('/opencode/claude', (req, res) => {
114
- return handleOpenCodeConvert(req, res, 'claude', convertClaudeToOpenCodePayload);
115
- });
116
-
117
- /**
118
- * Codex -> OpenCode
119
- * POST /api/convert/opencode/codex
120
- * Body: { payload, options? }
121
- */
122
- router.post('/opencode/codex', (req, res) => {
123
- return handleOpenCodeConvert(req, res, 'codex', convertCodexToOpenCodePayload);
124
- });
125
-
126
- /**
127
- * Gemini -> OpenCode
128
- * POST /api/convert/opencode/gemini
129
- * Body: { payload, options? }
130
- */
131
- router.post('/opencode/gemini', (req, res) => {
132
- return handleOpenCodeConvert(req, res, 'gemini', convertGeminiToOpenCodePayload);
90
+ router.post('/opencode/:sourceType', (req, res) => {
91
+ return handleOpenCodeConvert(req, res, req.params.sourceType);
133
92
  });
134
93
 
135
94
  /**
@@ -140,31 +99,29 @@ router.post('/opencode/gemini', (req, res) => {
140
99
  router.post('/opencode', (req, res) => {
141
100
  try {
142
101
  const { sourceType, payload, options = {} } = req.body || {};
102
+ const driver = getConversionDriver();
103
+ const sourceTypes = driver?.formats?.().sourceTypes || driver?.getFormats?.().sourceTypes || [];
104
+ const normalized = driver?.normalizeSourceType?.(sourceType) || sourceType;
143
105
 
106
+ if (!driver) {
107
+ return res.status(404).json({ success: false, error: 'Conversion is not supported' });
108
+ }
144
109
  if (!sourceType || !payload) {
145
110
  return res.status(400).json({
146
111
  success: false,
147
112
  error: 'Missing required parameters: sourceType, payload'
148
113
  });
149
114
  }
150
-
151
- const normalized = normalizeSourceType(sourceType);
152
- if (!SUPPORTED_SOURCE_TYPES.includes(normalized)) {
115
+ if (!sourceTypes.includes(normalized)) {
153
116
  return res.status(400).json({
154
117
  success: false,
155
- error: `Invalid sourceType: ${sourceType}. Must be one of: ${SUPPORTED_SOURCE_TYPES.join(', ')}`
118
+ error: `Invalid sourceType: ${sourceType}. Must be one of: ${sourceTypes.join(', ')}`
156
119
  });
157
120
  }
158
121
 
159
- const result = convertToOpenCodePayload({
160
- sourceType: normalized,
161
- payload,
162
- options
163
- });
164
-
165
122
  return res.json({
166
123
  success: true,
167
- ...result
124
+ ...driver.convert({ sourceType: normalized, payload, options })
168
125
  });
169
126
  } catch (error) {
170
127
  console.error('[Convert API] OpenCode gateway convert error:', error);
@@ -175,86 +132,5 @@ router.post('/opencode', (req, res) => {
175
132
  }
176
133
  });
177
134
 
178
- /**
179
- * 预览转换结果
180
- * POST /api/convert/preview
181
- * Body: { sourceType, sessionId }
182
- */
183
- router.post('/preview', async (req, res) => {
184
- try {
185
- const { sourceType, sessionId } = req.body;
186
-
187
- if (!sourceType || !sessionId) {
188
- return res.status(400).json({
189
- success: false,
190
- error: 'Missing required parameters: sourceType, sessionId'
191
- });
192
- }
193
-
194
- const preview = await previewConversion(sourceType, sessionId);
195
-
196
- res.json({
197
- success: true,
198
- preview
199
- });
200
- } catch (error) {
201
- console.error('[Convert API] Preview error:', error);
202
- res.status(500).json({
203
- success: false,
204
- error: error.message
205
- });
206
- }
207
- });
208
-
209
- /**
210
- * 执行会话转换
211
- * POST /api/convert
212
- * Body: { sourceType, targetType, sessionId, options }
213
- */
214
- router.post('/', async (req, res) => {
215
- try {
216
- const { sourceType, targetType, sessionId, options = {} } = req.body;
217
-
218
- // 验证必需参数
219
- if (!sourceType || !targetType || !sessionId) {
220
- return res.status(400).json({
221
- success: false,
222
- error: 'Missing required parameters: sourceType, targetType, sessionId'
223
- });
224
- }
225
-
226
- // 验证格式
227
- const validTypes = ['claude', 'codex', 'gemini'];
228
- if (!validTypes.includes(sourceType)) {
229
- return res.status(400).json({
230
- success: false,
231
- error: `Invalid sourceType: ${sourceType}. Must be one of: ${validTypes.join(', ')}`
232
- });
233
- }
234
- if (!validTypes.includes(targetType)) {
235
- return res.status(400).json({
236
- success: false,
237
- error: `Invalid targetType: ${targetType}. Must be one of: ${validTypes.join(', ')}`
238
- });
239
- }
240
- if (sourceType === targetType) {
241
- return res.status(400).json({
242
- success: false,
243
- error: 'Source and target types must be different'
244
- });
245
- }
246
-
247
- // 执行转换
248
- const result = await convertSession(sourceType, targetType, sessionId, options);
249
-
250
- res.json(result);
251
- } catch (error) {
252
- console.error('[Convert API] Conversion error:', error);
253
- res.status(500).json({
254
- success: false,
255
- error: error.message
256
- });
257
- }
258
- });
259
135
 
260
136
  module.exports = router;
@@ -1,19 +1,13 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
3
  const { loadConfig } = require('../../config/loader');
4
+ const { getPlatformContext } = require('../platform-context');
4
5
 
5
6
  // Services
6
7
  const { loadUIConfig } = require('../services/ui-config');
7
8
  const { loadFavorites } = require('../services/favorites');
8
- const { getProxyStatus } = require('../proxy-server');
9
- const { getCodexProxyStatus } = require('../codex-proxy-server');
10
- const { getGeminiProxyStatus } = require('../gemini-proxy-server');
11
- const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
12
- const { getOmpProxyStatus } = require('../omp-proxy-server');
13
9
  const { getSnapshot } = require('../services/snapshot-cache');
14
- const { projectCountKey } = require('../services/project-snapshots');
15
- const { runDashboardSnapshotWorker } = require('../services/dashboard-snapshot-worker');
16
-
10
+ const { runDashboardSourceWorker } = require('../services/dashboard-snapshot-worker');
17
11
  const SNAPSHOT_TTL_MS = 60 * 1000;
18
12
  const DASHBOARD_SNAPSHOT_DEFER_MS = 0;
19
13
  const EMPTY_COUNTS = { projectCount: 0, sessionCount: 0 };
@@ -28,12 +22,81 @@ function safeRead(label, reader, fallback) {
28
22
  }
29
23
  }
30
24
 
31
- function channelSnapshotKey(source) {
32
- return `dashboard:channels:${source}`;
25
+ function safeText(value, fallback = '') {
26
+ const text = typeof value === 'string'
27
+ ? value
28
+ : typeof value === 'number'
29
+ ? String(value)
30
+ : fallback;
31
+ return text.length > 1024 ? text.slice(0, 1024) : text;
32
+ }
33
+
34
+ function safeRetryAfter(value) {
35
+ return typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))
36
+ ? value
37
+ : undefined;
38
+ }
39
+ function safeCode(value) {
40
+ return typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))
41
+ ? value
42
+ : undefined;
33
43
  }
34
44
 
35
- function todayStatsSnapshotKey(source) {
36
- return `dashboard:today-stats:${source}`;
45
+
46
+ function resolveEnabledSources(uiConfig = {}, registry = getPlatformContext().registry) {
47
+ const definitions = registry?.list?.() || [];
48
+ const knownKeys = new Set(
49
+ definitions
50
+ .map(platform => String(platform?.key || '').trim().toLowerCase())
51
+ .filter(Boolean)
52
+ );
53
+ const requested = Array.isArray(uiConfig.enabledCliPlatforms)
54
+ ? uiConfig.enabledCliPlatforms
55
+ : (registry?.listDefaults?.({ enabledOnly: true }) || definitions)
56
+ .filter(platform => platform.defaultEnabled !== false)
57
+ .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
58
+ .map(platform => platform.key);
59
+ const seen = new Set();
60
+
61
+ return requested
62
+ .map(platform => String(platform || '').trim().toLowerCase())
63
+ .filter(platform => {
64
+ if (!knownKeys.has(platform) || seen.has(platform)) return false;
65
+ seen.add(platform);
66
+ return true;
67
+ });
68
+ }
69
+
70
+ async function readProxyStatus(source, runtime) {
71
+ try {
72
+ const driver = runtime?.getDriver?.(source, 'proxy');
73
+ if (!driver || typeof driver.status !== 'function') {
74
+ return { status: 'unsupported', platform: source, capability: 'proxy', operation: 'status' };
75
+ }
76
+ return await driver.status();
77
+ } catch (error) {
78
+ const failure = {
79
+ status: typeof error?.status === 'string' ? safeText(error.status, 'failed') : 'failed',
80
+ platform: source,
81
+ capability: 'proxy',
82
+ operation: 'status',
83
+ error: safeText(error && error.message ? error.message : error, 'proxy status failed')
84
+ };
85
+ if (typeof error?.retryable === 'boolean') failure.retryable = error.retryable;
86
+ const retryAfter = safeRetryAfter(error?.retryAfter);
87
+ if (retryAfter !== undefined) {
88
+ failure.retryAfter = retryAfter;
89
+ }
90
+ const code = safeCode(error?.code);
91
+ if (code !== undefined) {
92
+ failure.code = code;
93
+ }
94
+ return failure;
95
+ }
96
+ }
97
+
98
+ function sourceSnapshotKey(source) {
99
+ return `dashboard:source:${source}`;
37
100
  }
38
101
 
39
102
  function normalizeChannelTokens(tokens) {
@@ -73,18 +136,45 @@ function formatStats(stats) {
73
136
  return { ...EMPTY_STATS };
74
137
  }
75
138
 
76
- async function readDashboardSnapshot(kind, source, key, fallbackValue, config = {}, options = {}) {
139
+ async function readDashboardSnapshot(source, fallbackValue, config = {}, options = {}) {
77
140
  const force = options.force === true;
78
- return getSnapshot(key, {
141
+ const key = sourceSnapshotKey(source);
142
+ const makeReadOptions = (effectiveForce) => ({
79
143
  ttlMs: SNAPSHOT_TTL_MS,
80
144
  fallbackValue,
81
- force,
82
- staleWhileForce: force,
145
+ force: effectiveForce,
146
+ staleWhileForce: effectiveForce,
83
147
  deferMs: DASHBOARD_SNAPSHOT_DEFER_MS,
84
- refresh: () => runDashboardSnapshotWorker(kind, source, config, { force })
148
+ refresh: () => runDashboardSourceWorker(source, config, {
149
+ force: effectiveForce,
150
+ runtime: options.runtime
151
+ })
85
152
  });
153
+ const snapshot = await getSnapshot(key, makeReadOptions(force));
154
+ const generatedAtMs = Date.parse(snapshot.meta?.generatedAt || '');
155
+ const errorAgeMs = Number.isFinite(generatedAtMs) ? Date.now() - generatedAtMs : SNAPSHOT_TTL_MS;
156
+ if (!force
157
+ && errorAgeMs >= SNAPSHOT_TTL_MS
158
+ && Array.isArray(snapshot.value?.__errors)
159
+ && snapshot.value.__errors.length > 0) {
160
+ return getSnapshot(key, makeReadOptions(true));
161
+ }
162
+ return snapshot;
163
+ }
164
+
165
+ function sourceFallback(source, runtime) {
166
+ const channelsDriver = runtime?.getDriver?.(source, 'channels');
167
+ const channels = typeof channelsDriver?.normalizeDashboardChannels === 'function'
168
+ ? channelsDriver.normalizeDashboardChannels(null)
169
+ : { channels: [] };
170
+ return {
171
+ channels,
172
+ todayStats: null,
173
+ counts: EMPTY_COUNTS
174
+ };
86
175
  }
87
176
 
177
+
88
178
  function summarizePart(partSnapshots = {}) {
89
179
  const items = {};
90
180
  let stale = false;
@@ -125,104 +215,80 @@ function dashboardMeta(parts) {
125
215
  */
126
216
  router.get('/init', async (req, res) => {
127
217
  try {
128
- const config = loadConfig();
218
+ const config = safeRead('config', () => loadConfig(), {});
129
219
  const force = req.query?.fresh === '1' || req.query?.force === '1';
130
-
131
220
  const uiConfig = safeRead('uiConfig', () => loadUIConfig(), {});
132
221
  const favorites = safeRead('favorites', () => loadFavorites(), {});
133
- const proxyStatus = {
134
- claude: safeRead('claude proxy status', () => getProxyStatus(), {}),
135
- codex: safeRead('codex proxy status', () => getCodexProxyStatus(), {}),
136
- gemini: safeRead('gemini proxy status', () => getGeminiProxyStatus(), {}),
137
- opencode: safeRead('opencode proxy status', () => getOpenCodeProxyStatus(), {}),
138
- omp: safeRead('omp proxy status', () => getOmpProxyStatus(), {})
139
- };
140
-
141
- const [
142
- claudeChannels,
143
- codexChannels,
144
- geminiChannels,
145
- opencodeChannels,
146
- ompChannels,
147
- claudeTodayStats,
148
- codexTodayStats,
149
- geminiTodayStats,
150
- opencodeTodayStats,
151
- ompTodayStats,
152
- claudeCounts,
153
- codexCounts,
154
- geminiCounts,
155
- opencodeCounts,
156
- ompCounts
157
- ] = await Promise.all([
158
- readDashboardSnapshot('channels', 'claude', channelSnapshotKey('claude'), [], config, { force }),
159
- readDashboardSnapshot('channels', 'codex', channelSnapshotKey('codex'), { channels: [] }, config, { force }),
160
- readDashboardSnapshot('channels', 'gemini', channelSnapshotKey('gemini'), { channels: [] }, config, { force }),
161
- readDashboardSnapshot('channels', 'opencode', channelSnapshotKey('opencode'), { channels: [] }, config, { force }),
162
- readDashboardSnapshot('channels', 'omp', channelSnapshotKey('omp'), { channels: [] }, config, { force }),
163
- readDashboardSnapshot('todayStats', 'claude', todayStatsSnapshotKey('claude'), null, config, { force }),
164
- readDashboardSnapshot('todayStats', 'codex', todayStatsSnapshotKey('codex'), null, config, { force }),
165
- readDashboardSnapshot('todayStats', 'gemini', todayStatsSnapshotKey('gemini'), null, config, { force }),
166
- readDashboardSnapshot('todayStats', 'opencode', todayStatsSnapshotKey('opencode'), null, config, { force }),
167
- readDashboardSnapshot('todayStats', 'omp', todayStatsSnapshotKey('omp'), null, config, { force }),
168
- readDashboardSnapshot('counts', 'claude', projectCountKey('claude'), EMPTY_COUNTS, config, { force }),
169
- readDashboardSnapshot('counts', 'codex', projectCountKey('codex'), EMPTY_COUNTS, config, { force }),
170
- readDashboardSnapshot('counts', 'gemini', projectCountKey('gemini'), EMPTY_COUNTS, config, { force }),
171
- readDashboardSnapshot('counts', 'opencode', projectCountKey('opencode'), EMPTY_COUNTS, config, { force }),
172
- readDashboardSnapshot('counts', 'omp', projectCountKey('omp'), EMPTY_COUNTS, config, { force })
173
- ]);
222
+ const { registry, runtime } = getPlatformContext();
223
+ const sources = resolveEnabledSources(uiConfig, registry);
224
+ const sourceFallbackPayload = source => sourceFallback(source, runtime);
225
+ const proxyStatusEntries = await Promise.all(sources.map(async source => [
226
+ source,
227
+ await readProxyStatus(source, runtime)
228
+ ]));
229
+ const proxyStatus = Object.fromEntries(proxyStatusEntries);
230
+ const sourceSnapshots = await Promise.all(sources.map(async (source) => {
231
+ const snapshot = await readDashboardSnapshot(
232
+ source,
233
+ sourceFallbackPayload(source),
234
+ config,
235
+ { force, runtime }
236
+ );
237
+ return [source, snapshot];
238
+ }));
239
+ const snapshotsBySource = Object.fromEntries(sourceSnapshots);
174
240
 
241
+ const capabilitySnapshot = (source, capability) => {
242
+ const snapshot = snapshotsBySource[source];
243
+ const value = snapshot?.value || sourceFallbackPayload(source);
244
+ const failure = Array.isArray(value.__errors)
245
+ ? value.__errors.find(error => error.capability === capability
246
+ || (capability === 'todayStats' && error.capability === 'statistics'))
247
+ : null;
248
+ const meta = { ...(snapshot?.meta || {}) };
249
+ if (failure) {
250
+ meta.stale = true;
251
+ meta.error = failure.error || 'dashboard capability failed';
252
+ if (failure.retryable !== undefined) meta.retryable = failure.retryable;
253
+ if (failure.retryAfter !== undefined) meta.retryAfter = failure.retryAfter;
254
+ if (failure.code !== undefined) meta.code = failure.code;
255
+ }
256
+ return { value: value[capability], meta };
257
+ };
175
258
  const parts = {
176
- counts: summarizePart({
177
- claude: claudeCounts,
178
- codex: codexCounts,
179
- gemini: geminiCounts,
180
- opencode: opencodeCounts,
181
- omp: ompCounts
182
- }),
183
- todayStats: summarizePart({
184
- claude: claudeTodayStats,
185
- codex: codexTodayStats,
186
- gemini: geminiTodayStats,
187
- opencode: opencodeTodayStats,
188
- omp: ompTodayStats
189
- }),
190
- channels: summarizePart({
191
- claude: claudeChannels,
192
- codex: codexChannels,
193
- gemini: geminiChannels,
194
- opencode: opencodeChannels,
195
- omp: ompChannels
196
- })
259
+ counts: summarizePart(Object.fromEntries(sources.map(source => [source, capabilitySnapshot(source, 'counts')]))),
260
+ todayStats: summarizePart(Object.fromEntries(sources.map(source => [source, capabilitySnapshot(source, 'todayStats')]))),
261
+ channels: summarizePart(Object.fromEntries(sources.map(source => [source, capabilitySnapshot(source, 'channels')]))),
197
262
  };
263
+ const capabilityValueFor = (source, capability) => capabilitySnapshot(source, capability).value;
264
+ const channels = Object.fromEntries(sources.map(source => {
265
+ const value = capabilityValueFor(source, 'channels');
266
+ const driver = runtime?.getDriver?.(source, 'channels');
267
+ return [
268
+ source,
269
+ typeof driver?.normalizeDashboardChannels === 'function'
270
+ ? driver.normalizeDashboardChannels(value)
271
+ : value
272
+ ];
273
+ }));
274
+ const counts = Object.fromEntries(sources.map(source => [
275
+ source,
276
+ capabilityValueFor(source, 'counts') || EMPTY_COUNTS
277
+ ]));
278
+ const todayStats = Object.fromEntries(sources.map(source => [
279
+ source,
280
+ formatStats(capabilityValueFor(source, 'todayStats'))
281
+ ]));
198
282
 
199
283
  res.json({
200
284
  success: true,
201
285
  data: {
202
286
  uiConfig,
203
287
  favorites,
204
- channels: {
205
- claude: claudeChannels.value,
206
- codex: codexChannels.value,
207
- gemini: geminiChannels.value,
208
- opencode: opencodeChannels.value.channels || [],
209
- omp: ompChannels.value.channels || []
210
- },
288
+ channels,
211
289
  proxyStatus,
212
- counts: {
213
- claude: claudeCounts.value || EMPTY_COUNTS,
214
- codex: codexCounts.value || EMPTY_COUNTS,
215
- gemini: geminiCounts.value || EMPTY_COUNTS,
216
- opencode: opencodeCounts.value || EMPTY_COUNTS,
217
- omp: ompCounts.value || EMPTY_COUNTS
218
- },
219
- todayStats: {
220
- claude: formatStats(claudeTodayStats.value),
221
- codex: formatStats(codexTodayStats.value),
222
- gemini: formatStats(geminiTodayStats.value),
223
- opencode: formatStats(opencodeTodayStats.value),
224
- omp: formatStats(ompTodayStats.value)
225
- },
290
+ counts,
291
+ todayStats,
226
292
  meta: dashboardMeta(parts)
227
293
  }
228
294
  });
@@ -1,6 +1,6 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
- const notificationHooks = require('../services/notification-hooks');
3
+ const notificationHooks = require('../../platforms/notification-hooks');
4
4
  const { createSameOriginGuard, isLoopbackRequest } = require('../services/network-access');
5
5
 
6
6
  router.use(createSameOriginGuard({