coding-tool-x 3.8.4 → 3.8.6

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-Db9wAyaZ.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics-C4gMzv7g.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-Bd_nMFu3.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-CwzJHyNP.js +1 -0
  10. package/dist/web/assets/CollapseItem-CTCfSiue.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-vDghB1U9.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-C-XDvlHc.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-CYY8XRED.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-CbsJp76X.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-liMSTh7s.js +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-D6QSiYaL.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-RvET6iNG.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-P8eseZcf.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-CYmLGYju.js +1 -0
  29. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-Ch3bkKIq.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-BcTlgSpN.js +1 -0
  34. package/dist/web/assets/PluginManager-Dh2o0xkZ.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  36. package/dist/web/assets/PluginsDrawer-deKQ5u3I.js +1 -0
  37. package/dist/web/assets/PluginsPanel-BofsgQgj.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-DhSWJHTA.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-Cfb2FeeU.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-3eXamJhr.js +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  45. package/dist/web/assets/SessionCard-CW0eHURI.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-DQfyDGWU.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-Dw5NUgHC.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-u_vwUuPS.js +1 -0
  51. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  52. package/dist/web/assets/SkillManager-DHMprJLA.js +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-CkF3BpyC.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-DWE4pFGx.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-BgfG1Ked.js +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  59. package/dist/web/assets/Upload-CNwaGOws.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-Br5CeCzL.js +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  62. package/dist/web/assets/WorkspaceManager-B2O8-qIw.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-5oDKit3c.js +1 -0
  64. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +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-BMAUPq5K.js +2 -0
  71. package/dist/web/assets/index-Bi1MQHYX.css +1 -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-jbmqZuJs.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 +104 -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 +165 -0
  140. package/src/platforms/channel-auth-service.js +107 -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 +98 -418
  269. package/src/{server/services → platforms}/notification-hooks.js +190 -1023
  270. package/src/platforms/oauth-credentials-service.js +635 -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 +133 -24
  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 +12 -2
  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 +876 -214
  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
@@ -147,17 +147,27 @@ function runRefresh(refresh, defer, deferMs) {
147
147
 
148
148
  function refreshSnapshot(key, refresh, {
149
149
  defer = false,
150
- deferMs = DEFAULT_BACKGROUND_DELAY_MS,
151
- bypassInflight = false
150
+ deferMs = DEFAULT_BACKGROUND_DELAY_MS
152
151
  } = {}) {
153
- if (!bypassInflight && inflight.has(key)) {
152
+ if (inflight.has(key)) {
154
153
  return inflight.get(key);
155
154
  }
156
155
 
157
- const startedVersion = bypassInflight ? bumpInvalidationVersion(key) : getInvalidationVersion(key);
156
+ const startedVersion = getInvalidationVersion(key);
158
157
  const promise = runRefresh(refresh, defer, deferMs)
159
158
  .then((value) => {
160
- const entry = makeEntry(key, value);
159
+ let nextValue = value;
160
+ const existing = getEntry(key);
161
+ if (value && Array.isArray(value.__errors) && existing?.value && typeof existing.value === 'object') {
162
+ nextValue = { ...existing.value, ...value };
163
+ for (const failure of value.__errors) {
164
+ const capability = failure?.capability === 'statistics' ? 'todayStats' : failure?.capability;
165
+ if (capability && Object.prototype.hasOwnProperty.call(existing.value, capability)) {
166
+ nextValue[capability] = existing.value[capability];
167
+ }
168
+ }
169
+ }
170
+ const entry = makeEntry(key, nextValue);
161
171
  if (getInvalidationVersion(key) === startedVersion) {
162
172
  snapshots.set(key, entry);
163
173
  lastErrors.delete(key);
@@ -271,7 +281,7 @@ async function getSnapshot(key, {
271
281
  : 0;
272
282
 
273
283
  if (!entry && Number(coldWaitMs) > 0) {
274
- refreshPromise = refreshSnapshot(key, refresh, { bypassInflight: force });
284
+ refreshPromise = refreshSnapshot(key, refresh);
275
285
  const waitResult = await waitForRefresh(refreshPromise, coldWaitMs);
276
286
  if (waitResult.entry) {
277
287
  entry = waitResult.entry;
@@ -287,7 +297,7 @@ async function getSnapshot(key, {
287
297
  }
288
298
  } else if (!entry && backgroundOnMiss === false) {
289
299
  try {
290
- entry = await refreshSnapshot(key, refresh, { bypassInflight: force });
300
+ entry = await refreshSnapshot(key, refresh);
291
301
  expired = false;
292
302
  } catch (error) {
293
303
  refreshError = error?.message || String(error);
@@ -295,7 +305,7 @@ async function getSnapshot(key, {
295
305
  if (!entry) throw error;
296
306
  }
297
307
  } else {
298
- refreshPromise = refreshSnapshot(key, refresh, { defer: !force, deferMs, bypassInflight: force });
308
+ refreshPromise = refreshSnapshot(key, refresh, { defer: !force, deferMs });
299
309
  }
300
310
  }
301
311
 
@@ -370,6 +380,16 @@ function invalidateSnapshot(keyOrPrefix) {
370
380
  }
371
381
  }
372
382
 
383
+ function dashboardSourceKey(source) {
384
+ return `dashboard:source:${source}`;
385
+ }
386
+
387
+ function invalidateDashboardSourceSnapshot(source) {
388
+ if (source) {
389
+ invalidateSnapshot(dashboardSourceKey(source));
390
+ }
391
+ }
392
+
373
393
  function clearSnapshotCache({ deleteFiles = false } = {}) {
374
394
  for (const key of inflight.keys()) {
375
395
  bumpInvalidationVersion(key);
@@ -392,6 +412,8 @@ module.exports = {
392
412
  refreshSnapshot,
393
413
  invalidateSnapshot,
394
414
  clearSnapshotCache,
415
+ dashboardSourceKey,
416
+ invalidateDashboardSourceSnapshot,
395
417
  _test: {
396
418
  getSnapshotDir,
397
419
  getSnapshotPath,
@@ -8,18 +8,18 @@ const https = require('https');
8
8
  const http = require('http');
9
9
  const { URL } = require('url');
10
10
  const { probeModelAvailability } = require('./model-detector');
11
- const { getEffectiveApiKey: getClaudeEffectiveApiKey } = require('./channels');
12
- const { getEffectiveApiKey: getCodexEffectiveApiKey } = require('./codex-channels');
13
- const { getEffectiveApiKey: getGeminiEffectiveApiKey } = require('./gemini-channels');
14
- const { getEffectiveApiKey: getOpenCodeEffectiveApiKey } = require('./opencode-channels');
15
- const { getEffectiveApiKey: getOmpEffectiveApiKey } = require('./omp-channels');
16
- const { createCodexRequest } = require('./codex-wire');
17
- const { createClaudeRequest, buildClaudeTargetUrl } = require('./claude-wire');
11
+ const { getEffectiveApiKey: getClaudeEffectiveApiKey } = require('../../platforms/drivers/claude/channels-implementation');
12
+ const { getEffectiveApiKey: getCodexEffectiveApiKey } = require('../../platforms/drivers/codex/channels-implementation');
13
+ const { getEffectiveApiKey: getGeminiEffectiveApiKey } = require('../../platforms/drivers/gemini/channels-implementation');
14
+ const { getEffectiveApiKey: getOpenCodeEffectiveApiKey } = require('../../platforms/drivers/opencode/channels-implementation');
15
+ const { getEffectiveApiKey: getOmpEffectiveApiKey } = require('../../platforms/drivers/omp/channels-implementation');
16
+ const { createCodexRequest } = require('../../platforms/drivers/codex/wire');
17
+ const { createClaudeRequest, buildClaudeTargetUrl } = require('../../platforms/drivers/claude/wire');
18
18
  const {
19
19
  createGeminiRequest,
20
20
  buildGeminiTargetUrl,
21
21
  shouldUseGeminiCliFormat
22
- } = require('./gemini-wire');
22
+ } = require('../../platforms/drivers/gemini/wire');
23
23
 
24
24
  // 测试结果缓存
25
25
  const testResultsCache = new Map();
@@ -39,7 +39,17 @@ function openDatabase(filePath, options = {}) {
39
39
  });
40
40
 
41
41
  if (!options.readOnly) {
42
- db.exec('PRAGMA foreign_keys = ON');
42
+ // Keep the per-process SQLite footprint bounded. The session history
43
+ // database can contain hundreds of thousands of messages, and the
44
+ // default page cache plus an unbounded WAL made repeated inventories
45
+ // compete with the Node heap for memory and disk space.
46
+ db.exec(`
47
+ PRAGMA foreign_keys = ON;
48
+ PRAGMA journal_mode = WAL;
49
+ PRAGMA synchronous = NORMAL;
50
+ PRAGMA wal_autocheckpoint = 1000;
51
+ PRAGMA cache_size = -16384;
52
+ `);
43
53
  }
44
54
 
45
55
  _connections.set(key, db);
@@ -57,8 +67,13 @@ function closeDatabase(filePath) {
57
67
  if (!db) {
58
68
  return;
59
69
  }
60
- db.close();
61
- _connections.delete(key);
70
+ try {
71
+ // A clean shutdown should not leave a multi-gigabyte WAL behind. This is
72
+ // best effort because another process may still be using the database.
73
+ try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch (_err) {}
74
+ } finally {
75
+ try { db.close(); } finally { _connections.delete(key); }
76
+ }
62
77
  }
63
78
 
64
79
  /**
@@ -67,6 +82,7 @@ function closeDatabase(filePath) {
67
82
  function closeAllDatabases() {
68
83
  for (const db of _connections.values()) {
69
84
  try {
85
+ try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch (_err) {}
70
86
  db.close();
71
87
  } catch (_err) {
72
88
  // swallow — best-effort teardown
@@ -1,11 +1,17 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { PATHS } = require('../../config/paths');
4
- const { normalizeUsageTokens, resolveActualModel } = require('./proxy-log-helper');
4
+ const { normalizeUsageTokens, resolveActualModel } = require('./usage-log-utils');
5
5
 
6
6
  // 北京时间辅助(UTC+8),统一所有时间计算
7
7
  const CST_OFFSET_MS = 8 * 60 * 60 * 1000;
8
8
 
9
+ const pendingEntries = [];
10
+ let flushPromise = null;
11
+ let flushScheduled = false;
12
+ let aggregateStatsCache = null;
13
+ const dailyStatsCache = new Map();
14
+
9
15
  function toCSTDate(ts) {
10
16
  // 返回以北京时间解释的 Date 对象各字段(通过偏移 UTC)
11
17
  return new Date(new Date(ts).getTime() + CST_OFFSET_MS);
@@ -181,6 +187,111 @@ function appendRequestLog(logEntry) {
181
187
  }
182
188
  }
183
189
 
190
+ function cloneJson(value) {
191
+ return value == null ? value : JSON.parse(JSON.stringify(value));
192
+ }
193
+
194
+ function getRequestLogPath(timestamp) {
195
+ const cst = toCSTDate(timestamp);
196
+ const year = cst.getUTCFullYear();
197
+ const month = String(cst.getUTCMonth() + 1).padStart(2, '0');
198
+ const day = String(cst.getUTCDate()).padStart(2, '0');
199
+ return path.join(PATHS.statistics.requestLogs, `${year}-${month}`, `${day}.jsonl`);
200
+ }
201
+
202
+ async function appendRequestLogsAsync(entries) {
203
+ const byPath = new Map();
204
+ for (const entry of entries) {
205
+ const filePath = getRequestLogPath(entry.logEntry.timestamp);
206
+ const group = byPath.get(filePath) || [];
207
+ group.push(entry);
208
+ byPath.set(filePath, group);
209
+ }
210
+
211
+ const persisted = [];
212
+ for (const [filePath, group] of byPath) {
213
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
214
+ await fs.promises.appendFile(
215
+ filePath,
216
+ group.map(entry => `${JSON.stringify(entry.logEntry)}\n`).join(''),
217
+ 'utf8'
218
+ );
219
+ persisted.push(...group);
220
+ }
221
+ return persisted;
222
+ }
223
+
224
+ async function writeJsonAsync(filePath, value) {
225
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
226
+ await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), 'utf8');
227
+ }
228
+
229
+ function scheduleStatisticsFlush() {
230
+ if (flushScheduled) return;
231
+ flushScheduled = true;
232
+ queueMicrotask(() => {
233
+ flushScheduled = false;
234
+ void flushStatistics();
235
+ });
236
+ }
237
+
238
+ async function flushStatistics() {
239
+ if (flushPromise) return flushPromise;
240
+ if (pendingEntries.length === 0) {
241
+ return { flushed: 0, pending: 0 };
242
+ }
243
+
244
+ const batch = pendingEntries.splice(0, pendingEntries.length);
245
+ const globalSnapshot = aggregateStatsCache ? cloneJson(aggregateStatsCache) : null;
246
+ const dates = [...new Set(batch.map(entry => entry.date))];
247
+ const dailySnapshots = new Map(
248
+ dates.map(date => [date, cloneJson(dailyStatsCache.get(date))])
249
+ );
250
+
251
+ const writer = (async () => {
252
+ try {
253
+ const pendingLogEntries = batch.filter(entry => !entry.logPersisted);
254
+ const persisted = await appendRequestLogsAsync(pendingLogEntries);
255
+ persisted.forEach(entry => { entry.logPersisted = true; });
256
+ if (globalSnapshot) {
257
+ globalSnapshot.lastUpdated = new Date().toISOString();
258
+ await writeJsonAsync(getStatisticsFilePath(), globalSnapshot);
259
+ }
260
+ for (const [date, stats] of dailySnapshots) {
261
+ if (stats) await writeJsonAsync(getDailyStatsFilePathWithoutCreate(date), stats);
262
+ }
263
+ return { flushed: batch.length, pending: pendingEntries.length };
264
+ } catch (error) {
265
+ pendingEntries.unshift(...batch);
266
+ console.error('[Statistics] Failed to flush queued entries:', error.message);
267
+ return {
268
+ flushed: 0,
269
+ pending: pendingEntries.length,
270
+ error: error.message
271
+ };
272
+ }
273
+ })();
274
+
275
+ flushPromise = writer.finally(() => {
276
+ flushPromise = null;
277
+ if (pendingEntries.length > 0) scheduleStatisticsFlush();
278
+ });
279
+ return flushPromise;
280
+ }
281
+
282
+ function getDailyStatsFilePathWithoutCreate(date) {
283
+ return path.join(PATHS.statistics.dailyStats, `${date}.json`);
284
+ }
285
+
286
+ async function shutdownStatistics() {
287
+ let result = await flushStatistics();
288
+ while (result.pending > 0 && !result.error) {
289
+ result = await flushStatistics();
290
+ }
291
+ return result;
292
+ }
293
+
294
+
184
295
  // 初始化统计对象
185
296
  function initStatsObject() {
186
297
  return {
@@ -257,11 +368,10 @@ function recordRequest(requestData) {
257
368
  if (requestData.redirectedModel) {
258
369
  logEntry.redirectedModel = requestData.redirectedModel;
259
370
  }
260
- appendRequestLog(logEntry);
261
371
 
262
372
  // 2. 更新总体统计
263
- const globalStats = loadStatistics();
264
-
373
+ const globalStats = aggregateStatsCache || loadStatistics();
374
+ aggregateStatsCache = globalStats;
265
375
  // 更新全局统计
266
376
  globalStats.global.totalRequests += 1;
267
377
  globalStats.global.totalTokens += totalTokens;
@@ -277,18 +387,22 @@ function recordRequest(requestData) {
277
387
  }
278
388
  updateStats(globalStats.byToolType[toolType], tokens, cost);
279
389
 
280
- // 按工具类型 -> 渠道统计
281
- if (!globalStats.byToolType[toolType].channels[channelId]) {
282
- globalStats.byToolType[toolType].channels[channelId] = {
283
- name: channel,
284
- ...initStatsObject(),
285
- firstUsed: timestamp,
286
- lastUsed: timestamp
287
- };
288
- } else {
289
- globalStats.byToolType[toolType].channels[channelId].lastUsed = timestamp;
390
+ // Native CLI logs do not always contain a reliable configured-channel id.
391
+ // Keep aggregate totals in that case instead of creating an "undefined"
392
+ // channel bucket.
393
+ if (channelId) {
394
+ if (!globalStats.byToolType[toolType].channels[channelId]) {
395
+ globalStats.byToolType[toolType].channels[channelId] = {
396
+ name: channel,
397
+ ...initStatsObject(),
398
+ firstUsed: timestamp,
399
+ lastUsed: timestamp
400
+ };
401
+ } else {
402
+ globalStats.byToolType[toolType].channels[channelId].lastUsed = timestamp;
403
+ }
404
+ updateStats(globalStats.byToolType[toolType].channels[channelId], tokens, cost);
290
405
  }
291
- updateStats(globalStats.byToolType[toolType].channels[channelId], tokens, cost);
292
406
 
293
407
  // 按工具类型 -> 模型统计
294
408
  if (!globalStats.byToolType[toolType].models[modelKey]) {
@@ -296,19 +410,21 @@ function recordRequest(requestData) {
296
410
  }
297
411
  updateStats(globalStats.byToolType[toolType].models[modelKey], tokens, cost);
298
412
 
299
- // 按渠道统计(跨工具)
300
- if (!globalStats.byChannel[channelId]) {
301
- globalStats.byChannel[channelId] = {
302
- toolType,
303
- name: channel,
304
- ...initStatsObject(),
305
- firstUsed: timestamp,
306
- lastUsed: timestamp
307
- };
308
- } else {
309
- globalStats.byChannel[channelId].lastUsed = timestamp;
413
+ // Cross-tool channel totals follow the same rule.
414
+ if (channelId) {
415
+ if (!globalStats.byChannel[channelId]) {
416
+ globalStats.byChannel[channelId] = {
417
+ toolType,
418
+ name: channel,
419
+ ...initStatsObject(),
420
+ firstUsed: timestamp,
421
+ lastUsed: timestamp
422
+ };
423
+ } else {
424
+ globalStats.byChannel[channelId].lastUsed = timestamp;
425
+ }
426
+ updateStats(globalStats.byChannel[channelId], tokens, cost);
310
427
  }
311
- updateStats(globalStats.byChannel[channelId], tokens, cost);
312
428
 
313
429
  // 按模型统计(跨工具)
314
430
  if (!globalStats.byModel[modelKey]) {
@@ -319,13 +435,13 @@ function recordRequest(requestData) {
319
435
  }
320
436
  updateStats(globalStats.byModel[modelKey], tokens, cost);
321
437
 
322
- saveStatistics(globalStats);
323
438
 
324
439
  // 3. 更新每日统计(使用北京时间)
325
440
  const date = getCSTDateStr(timestamp); // YYYY-MM-DD (CST)
326
441
  const hour = getCSTHour(timestamp).toString().padStart(2, '0'); // HH (CST)
327
442
 
328
- const dailyStats = loadDailyStats(date);
443
+ const dailyStats = dailyStatsCache.get(date) || loadDailyStats(date);
444
+ dailyStatsCache.set(date, dailyStats);
329
445
 
330
446
  // 更新每日汇总
331
447
  dailyStats.summary.requests += 1;
@@ -357,17 +473,19 @@ function recordRequest(requestData) {
357
473
  }
358
474
  updateStats(dailyStats.byToolType[toolType], tokens, cost);
359
475
 
360
- // 按工具类型 -> 渠道
361
- if (!dailyStats.byToolType[toolType].channels) {
362
- dailyStats.byToolType[toolType].channels = {};
363
- }
364
- if (!dailyStats.byToolType[toolType].channels[channelId]) {
365
- dailyStats.byToolType[toolType].channels[channelId] = {
366
- name: channel,
367
- ...initStatsObject()
368
- };
476
+ // 按工具类型 -> 渠道;原生日志没有可靠渠道时只保留平台级统计。
477
+ if (channelId) {
478
+ if (!dailyStats.byToolType[toolType].channels) {
479
+ dailyStats.byToolType[toolType].channels = {};
480
+ }
481
+ if (!dailyStats.byToolType[toolType].channels[channelId]) {
482
+ dailyStats.byToolType[toolType].channels[channelId] = {
483
+ name: channel,
484
+ ...initStatsObject()
485
+ };
486
+ }
487
+ updateStats(dailyStats.byToolType[toolType].channels[channelId], tokens, cost);
369
488
  }
370
- updateStats(dailyStats.byToolType[toolType].channels[channelId], tokens, cost);
371
489
 
372
490
  // 按工具类型 -> 模型
373
491
  if (!dailyStats.byToolType[toolType].models) {
@@ -378,15 +496,17 @@ function recordRequest(requestData) {
378
496
  }
379
497
  updateStats(dailyStats.byToolType[toolType].models[modelKey], tokens, cost);
380
498
 
381
- // 按渠道统计
382
- if (!dailyStats.byChannel[channelId]) {
383
- dailyStats.byChannel[channelId] = {
384
- toolType,
385
- name: channel,
386
- ...initStatsObject()
387
- };
499
+ // 按渠道统计;不创建 undefined 渠道桶。
500
+ if (channelId) {
501
+ if (!dailyStats.byChannel[channelId]) {
502
+ dailyStats.byChannel[channelId] = {
503
+ toolType,
504
+ name: channel,
505
+ ...initStatsObject()
506
+ };
507
+ }
508
+ updateStats(dailyStats.byChannel[channelId], tokens, cost);
388
509
  }
389
- updateStats(dailyStats.byChannel[channelId], tokens, cost);
390
510
 
391
511
  // 按模型统计
392
512
  if (!dailyStats.byModel[modelKey]) {
@@ -397,10 +517,16 @@ function recordRequest(requestData) {
397
517
  }
398
518
  updateStats(dailyStats.byModel[modelKey], tokens, cost);
399
519
 
400
- saveDailyStats(date, dailyStats);
401
520
 
402
521
  // Invalidate cached trend results that cover this date
403
522
  invalidateTrendCacheForDate(date);
523
+ pendingEntries.push({ logEntry, date });
524
+ if (pendingEntries.length >= 1000) {
525
+ console.error('[Statistics] Write queue reached its limit; flushing immediately');
526
+ void flushStatistics();
527
+ } else {
528
+ scheduleStatisticsFlush();
529
+ }
404
530
  } catch (err) {
405
531
  console.error('[Statistics] Failed to record request:', err);
406
532
  }
@@ -410,14 +536,14 @@ function recordRequest(requestData) {
410
536
  * 获取统计数据
411
537
  */
412
538
  function getStatistics() {
413
- return loadStatistics();
539
+ return aggregateStatsCache ? cloneJson(aggregateStatsCache) : loadStatistics();
414
540
  }
415
541
 
416
542
  /**
417
543
  * 获取每日统计
418
544
  */
419
545
  function getDailyStatistics(date) {
420
- return aggregateDailyStatistics(date);
546
+ return aggregateDailyStatistics(date, dailyStatsCache.get(date) || null);
421
547
  }
422
548
 
423
549
  /**
@@ -425,7 +551,7 @@ function getDailyStatistics(date) {
425
551
  */
426
552
  function getTodayStatistics() {
427
553
  const today = getCSTDateStr(Date.now());
428
- return aggregateDailyStatistics(today);
554
+ return getDailyStatistics(today);
429
555
  }
430
556
 
431
557
  /**
@@ -448,6 +574,35 @@ function extractMetric(stats, metric) {
448
574
  * @param {string} groupBy
449
575
  * @param {Object} [filters] - optional { toolType, channel, model }
450
576
  */
577
+ function addLogEntryToResult(result, entry, groupBy, filters = null) {
578
+ const actualModel = resolveActualModel(entry.model, entry);
579
+ if (filters) {
580
+ if (filters.toolType && entry.toolType !== filters.toolType) return;
581
+ if (filters.channel && entry.channel !== filters.channel) return;
582
+ if (filters.model && actualModel !== filters.model) return;
583
+ }
584
+
585
+ let key;
586
+ if (groupBy === 'model') key = actualModel || 'unknown';
587
+ else if (groupBy === 'channel') key = entry.channel || entry.channelId || 'unknown';
588
+ else if (groupBy === 'toolType') key = entry.toolType || 'claude-code';
589
+ else return;
590
+
591
+ if (!result[key]) result[key] = { tokens: { total: 0 }, cost: 0, requests: 0 };
592
+ result[key].tokens.total += getTokenTotal(entry.tokens);
593
+ result[key].cost += entry.cost || 0;
594
+ result[key].requests += 1;
595
+ }
596
+
597
+ function mergePendingLogEntries(result, dateStr, hour, groupBy, filters = null) {
598
+ for (const { logEntry } of pendingEntries) {
599
+ const timestamp = new Date(logEntry.timestamp);
600
+ if (getCSTDateStr(timestamp) !== dateStr) continue;
601
+ if (hour !== undefined && getCSTHour(timestamp) !== hour) continue;
602
+ addLogEntryToResult(result, logEntry, groupBy, filters);
603
+ }
604
+ }
605
+
451
606
  function readJsonlForHour(year, month, day, hour, groupBy, filters) {
452
607
  const filePath = getRequestLogFilePath(year, month, day);
453
608
  const result = {};
@@ -488,6 +643,8 @@ function readJsonlForHour(year, month, day, hour, groupBy, filters) {
488
643
  console.error('Failed to read JSONL for hour:', err);
489
644
  }
490
645
 
646
+ const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
647
+ mergePendingLogEntries(result, dateStr, hour, groupBy, filters);
491
648
  return result;
492
649
  }
493
650
 
@@ -536,6 +693,8 @@ function readJsonlForDay(year, month, day, groupBy, filters) {
536
693
  console.error('Failed to read JSONL for day:', err);
537
694
  }
538
695
 
696
+ const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
697
+ mergePendingLogEntries(result, dateStr, undefined, groupBy, filters);
539
698
  return result;
540
699
  }
541
700
 
@@ -629,7 +788,22 @@ const TOOL_PREFIXES = {
629
788
  function getTokenTotal(tokens) {
630
789
  if (typeof tokens === 'number') return tokens;
631
790
  if (tokens && typeof tokens === 'object') {
632
- if (typeof tokens.total === 'number') return tokens.total;
791
+ if (typeof tokens.total === 'number' && tokens.total > 0) return tokens.total;
792
+
793
+ // Token detail fields overlap: cached/reasoning tokens are usually
794
+ // sub-counts of input/output. The canonical shape stores cached input
795
+ // separately, so include it once and never add reasoning independently.
796
+ const usageKeys = ['input', 'output', 'cacheCreation', 'cacheRead', 'cached'];
797
+ if (usageKeys.some(key => typeof tokens[key] === 'number')) {
798
+ const input = typeof tokens.input === 'number' ? tokens.input : 0;
799
+ const output = typeof tokens.output === 'number' ? tokens.output : 0;
800
+ const cacheCreation = typeof tokens.cacheCreation === 'number' ? tokens.cacheCreation : 0;
801
+ const cacheRead = typeof tokens.cacheRead === 'number'
802
+ ? tokens.cacheRead
803
+ : (typeof tokens.cached === 'number' ? tokens.cached : 0);
804
+ return input + output + cacheCreation + cacheRead;
805
+ }
806
+
633
807
  return Object.entries(tokens).reduce((sum, [key, value]) => {
634
808
  if (key === 'total') return sum;
635
809
  return typeof value === 'number' ? sum + value : sum;
@@ -725,7 +899,7 @@ function hasStatsData(stats = {}) {
725
899
  return requests > 0 || cost > 0 || totalTokens > 0;
726
900
  }
727
901
 
728
- function aggregateDailyStatistics(dateStr) {
902
+ function aggregateDailyStatistics(dateStr, sharedStatsOverride = null) {
729
903
  const aggregated = {
730
904
  date: dateStr,
731
905
  summary: {
@@ -739,7 +913,7 @@ function aggregateDailyStatistics(dateStr) {
739
913
  byModel: {}
740
914
  };
741
915
 
742
- const sharedStats = loadDailyStats(dateStr);
916
+ const sharedStats = sharedStatsOverride || loadDailyStats(dateStr);
743
917
  const sharedSummaryEntry = createEmptyEntry();
744
918
  mergeStatsEntry(sharedSummaryEntry, {
745
919
  requests: sharedStats.summary?.requests || 0,
@@ -814,7 +988,7 @@ function aggregateDailyStatistics(dateStr) {
814
988
  function mergeAllToolsDailyStats(dateStr, groupBy) {
815
989
  const merged = {};
816
990
 
817
- const aggregated = aggregateDailyStatistics(dateStr);
991
+ const aggregated = aggregateDailyStatistics(dateStr, dailyStatsCache.get(dateStr) || null);
818
992
  if (!aggregated) return merged;
819
993
 
820
994
  if (groupBy === 'toolType') {
@@ -891,6 +1065,13 @@ function getAvailableFilters(startDate, endDate) {
891
1065
  }
892
1066
  }
893
1067
 
1068
+ for (const { logEntry, date } of pendingEntries) {
1069
+ if (date < startDate || date > endDate) continue;
1070
+ if (logEntry.toolType) toolTypes.add(logEntry.toolType);
1071
+ if (logEntry.channel) channels.add(logEntry.channel);
1072
+ const actualModel = resolveActualModel(logEntry.model, logEntry);
1073
+ if (actualModel) models.add(actualModel);
1074
+ }
894
1075
  if (includeProxyLogs) {
895
1076
  for (const entry of loadProxyLogs()) {
896
1077
  if (!entry || entry.type === 'action' || entry.status === 'error') continue;
@@ -1029,7 +1210,7 @@ async function getTrendStatistics({ startDate, endDate, granularity = 'day', ste
1029
1210
  if (activeFilters) {
1030
1211
  byDimension = readJsonlForHour(year, month, day, hh, groupBy, activeFilters);
1031
1212
  } else if (groupBy === 'toolType') {
1032
- const dailyStats = aggregateDailyStatistics(dateStr);
1213
+ const dailyStats = aggregateDailyStatistics(dateStr, dailyStatsCache.get(dateStr) || null);
1033
1214
  const hourData = dailyStats?.hourly?.[hhStr];
1034
1215
  for (const [toolType, toolStats] of Object.entries(hourData?.byToolType || {})) {
1035
1216
  if (!hasStatsData(toolStats)) continue;
@@ -1095,5 +1276,7 @@ module.exports = {
1095
1276
  getDailyStatistics,
1096
1277
  getTodayStatistics,
1097
1278
  getTrendStatistics,
1098
- getAvailableFilters
1279
+ getAvailableFilters,
1280
+ flushStatistics,
1281
+ shutdownStatistics
1099
1282
  };