coding-tool-x 3.8.3 → 3.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
  10. package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-D7CCOFQC.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  29. package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
  34. package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
  36. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  37. package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
  45. package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
  51. package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
  52. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
  59. package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
  62. package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  64. package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-C0EHRym0.css +1 -0
  71. package/dist/web/assets/index-DnEocV0M.js +2 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +13 -8
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +94 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +162 -0
  140. package/src/platforms/channel-auth-service.js +82 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
  269. package/src/{server/services → platforms}/notification-hooks.js +140 -1023
  270. package/src/platforms/oauth-credentials-service.js +506 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +11 -20
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +1 -1
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +780 -213
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -1,810 +0,0 @@
1
- const express = require('express');
2
- const router = express.Router();
3
- const path = require('path');
4
- const fs = require('fs');
5
- const readline = require('readline');
6
- const { deleteSession, forkSession, saveSessionOrder, parseRealProjectPath, searchSessions, getRecentSessions, searchSessionsAcrossProjects, hasActualMessages } = require('../services/sessions');
7
- const { getSessionStatus, getSessionOutline, getMessagePage } = require('../services/session-history-index');
8
- const { loadAliases } = require('../services/alias');
9
- const { broadcastLog } = require('../websocket-server');
10
- const { NATIVE_PATHS } = require('../../config/paths');
11
- const {
12
- defaultProjectInfo,
13
- emptySessionList,
14
- getSessionListSnapshot,
15
- invalidateSessionSnapshots,
16
- runSessionSnapshotWorker
17
- } = require('../services/session-snapshots');
18
- const { invalidateProjectSnapshots } = require('../services/project-snapshots');
19
- const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
20
-
21
- function resolveClaudeSessionFile(projectName, sessionId, fullPath) {
22
- const possiblePaths = [
23
- path.join(fullPath, '.claude', 'sessions', sessionId + '.jsonl'),
24
- path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
25
- ];
26
-
27
- for (const testPath of possiblePaths) {
28
- if (fs.existsSync(testPath)) {
29
- return {
30
- sessionFile: testPath,
31
- triedPaths: possiblePaths
32
- };
33
- }
34
- }
35
-
36
- return {
37
- sessionFile: null,
38
- triedPaths: possiblePaths
39
- };
40
- }
41
-
42
- function getClaudeUserText(content) {
43
- if (typeof content === 'string') {
44
- return content === 'Warmup' ? '' : content;
45
- }
46
-
47
- if (!Array.isArray(content)) {
48
- return '';
49
- }
50
-
51
- const parts = [];
52
- for (const item of content) {
53
- if (item?.type === 'text' && item.text) {
54
- parts.push(item.text);
55
- } else if (item?.type === 'image') {
56
- parts.push('[图片]');
57
- }
58
- }
59
-
60
- return parts.join('\n\n').trim();
61
- }
62
-
63
- function buildOutlinePreview(text = '') {
64
- const firstLine = String(text)
65
- .split('\n')
66
- .map(line => line.trim())
67
- .find(Boolean) || '(空消息)';
68
- return firstLine.length > 42 ? `${firstLine.slice(0, 42)}...` : firstLine;
69
- }
70
-
71
- async function readClaudeSessionOutline(sessionFile) {
72
- const outline = [];
73
- const stream = fs.createReadStream(sessionFile, { encoding: 'utf8' });
74
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
75
- let userMessageNumber = 0;
76
-
77
- try {
78
- for await (const line of rl) {
79
- if (!line.trim()) continue;
80
-
81
- let json;
82
- try {
83
- json = JSON.parse(line);
84
- } catch (err) {
85
- continue;
86
- }
87
-
88
- if (json.type !== 'user') {
89
- continue;
90
- }
91
-
92
- const userText = getClaudeUserText(json.message?.content);
93
- if (!userText) {
94
- continue;
95
- }
96
-
97
- userMessageNumber += 1;
98
- outline.push({
99
- userMessageNumber,
100
- preview: buildOutlinePreview(userText),
101
- timestamp: json.timestamp || null
102
- });
103
- }
104
- } finally {
105
- rl.close();
106
- stream.destroy();
107
- }
108
-
109
- return outline;
110
- }
111
-
112
- function normalizeForkOptions(body = {}) {
113
- const alias = typeof body.alias === 'string' ? body.alias.trim() : '';
114
- const rawNumber = body.afterUserMessageNumber;
115
- const hasExplicitNumber = rawNumber !== undefined && rawNumber !== null && rawNumber !== '';
116
- const parsedNumber = hasExplicitNumber ? parseInt(rawNumber, 10) : null;
117
-
118
- if (hasExplicitNumber && (!Number.isInteger(parsedNumber) || parsedNumber <= 0)) {
119
- const error = new Error('afterUserMessageNumber must be a positive integer');
120
- error.statusCode = 400;
121
- throw error;
122
- }
123
-
124
- return {
125
- afterUserMessageNumber: parsedNumber,
126
- alias: alias || null
127
- };
128
- }
129
-
130
- module.exports = (config) => {
131
- // GET /api/sessions/search/global - Search sessions across all projects
132
- router.get('/search/global', async (req, res) => {
133
- try {
134
- const { keyword, context } = req.query;
135
-
136
- if (!keyword) {
137
- return res.status(400).json({ error: 'Keyword is required' });
138
- }
139
-
140
- const contextLength = context ? parseInt(context) : 35;
141
- const results = await searchSessionsAcrossProjects(config, keyword, contextLength);
142
-
143
- res.json({
144
- keyword,
145
- totalMatches: results.reduce((sum, r) => sum + r.matchCount, 0),
146
- sessions: results
147
- });
148
- } catch (error) {
149
- console.error('Error searching sessions globally:', error);
150
- res.status(500).json({ error: error.message });
151
- }
152
- });
153
-
154
- // GET /api/sessions/recent - Get recent sessions across all projects
155
- router.get('/recent/list', async (req, res) => {
156
- try {
157
- const limit = parseInt(req.query.limit) || 5;
158
- const sessions = await getRecentSessions(config, limit);
159
- res.json({ sessions });
160
- } catch (error) {
161
- console.error('Error fetching recent sessions:', error);
162
- res.status(500).json({ error: error.message });
163
- }
164
- });
165
-
166
- // GET /api/sessions/:projectName - Get sessions for a project
167
- router.get('/:projectName', async (req, res) => {
168
- try {
169
- const { projectName } = req.params;
170
- const force = req.query?.fresh === '1';
171
- const snapshot = await getSessionListSnapshot('claude', projectName, {
172
- fallbackValue: emptySessionList(projectName, {
173
- aliases: loadAliases(),
174
- projectInfo: defaultProjectInfo(projectName)
175
- }),
176
- force,
177
- refresh: () => runSessionSnapshotWorker('claude', projectName, config, { force })
178
- });
179
- res.json({ ...snapshot.value, meta: snapshot.meta });
180
- } catch (error) {
181
- console.error('Error fetching sessions:', error);
182
- res.status(500).json({ error: error.message });
183
- }
184
- });
185
-
186
- // DELETE /api/sessions/:projectName/:sessionId - Delete a session
187
- router.delete('/:projectName/:sessionId', (req, res) => {
188
- try {
189
- const { projectName, sessionId } = req.params;
190
- const result = deleteSession(config, projectName, sessionId);
191
- invalidateSessionSnapshots('claude', projectName);
192
- invalidateProjectSnapshots('claude');
193
- res.json(result);
194
- } catch (error) {
195
- console.error('Error deleting session:', error);
196
- res.status(500).json({ error: error.message });
197
- }
198
- });
199
-
200
- // POST /api/sessions/:projectName/batch-delete - Delete multiple sessions
201
- router.post('/:projectName/batch-delete', (req, res) => {
202
- try {
203
- const { projectName } = req.params;
204
- const { sessionIds } = req.body || {};
205
-
206
- if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
207
- return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
208
- }
209
-
210
- const uniqueSessionIds = Array.from(new Set(
211
- sessionIds
212
- .filter(sessionId => typeof sessionId === 'string')
213
- .map(sessionId => sessionId.trim())
214
- .filter(Boolean)
215
- ));
216
-
217
- if (uniqueSessionIds.length === 0) {
218
- return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
219
- }
220
-
221
- const deletedSessionIds = [];
222
- const failed = [];
223
-
224
- uniqueSessionIds.forEach((sessionId) => {
225
- try {
226
- deleteSession(config, projectName, sessionId);
227
- deletedSessionIds.push(sessionId);
228
- } catch (error) {
229
- failed.push({
230
- sessionId,
231
- error: error.message
232
- });
233
- }
234
- });
235
-
236
- if (deletedSessionIds.length > 0) {
237
- invalidateSessionSnapshots('claude', projectName);
238
- invalidateProjectSnapshots('claude');
239
- }
240
-
241
- res.json({
242
- success: failed.length === 0,
243
- requestedCount: uniqueSessionIds.length,
244
- deletedCount: deletedSessionIds.length,
245
- deletedSessionIds,
246
- failed
247
- });
248
- } catch (error) {
249
- console.error('Error batch deleting sessions:', error);
250
- res.status(500).json({ error: error.message });
251
- }
252
- });
253
-
254
- // POST /api/sessions/:projectName/:sessionId/fork - Fork a session
255
- router.post('/:projectName/:sessionId/fork', (req, res) => {
256
- try {
257
- const { projectName, sessionId } = req.params;
258
- const result = forkSession(config, projectName, sessionId, normalizeForkOptions(req.body));
259
- invalidateSessionSnapshots('claude', projectName);
260
- invalidateProjectSnapshots('claude');
261
- res.json(result);
262
- } catch (error) {
263
- console.error('Error forking session:', error);
264
- res.status(error.statusCode || 500).json({ error: error.message });
265
- }
266
- });
267
-
268
- // POST /api/sessions/:projectName/create - Create a new session
269
- router.post('/:projectName/create', (req, res) => {
270
- try {
271
- const { projectName } = req.params;
272
- const { toolType = 'claude' } = req.body; // 'claude', 'codex', 或 'gemini'
273
- const crypto = require('crypto');
274
-
275
- // 解析项目路径
276
- const { fullPath } = parseRealProjectPath(projectName);
277
-
278
- // 生成新的 session ID
279
- const newSessionId = crypto.randomUUID();
280
-
281
- // 根据工具类型决定会话文件路径
282
- let sessionDir, sessionFile;
283
-
284
- if (toolType === 'claude') {
285
- // Claude Code: 直接创建在项目的 .claude/sessions/ 目录(与 Claude Code 默认行为一致)
286
- sessionDir = path.join(fullPath, '.claude', 'sessions');
287
- sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
288
- } else if (toolType === 'codex') {
289
- // Codex: ~/.codex/sessions/YYYY/MM/DD/{sessionId}.jsonl
290
- const now = new Date();
291
- const year = now.getFullYear();
292
- const month = String(now.getMonth() + 1).padStart(2, '0');
293
- const day = String(now.getDate()).padStart(2, '0');
294
- sessionDir = path.join(NATIVE_PATHS.codex.sessions, String(year), month, day);
295
- sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
296
- } else if (toolType === 'gemini') {
297
- // Gemini: ~/.gemini/tmp/{hash}/chats/{sessionId}.json
298
- const pathHash = crypto.createHash('sha256').update(fullPath).digest('hex');
299
- sessionDir = path.join(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
300
- sessionFile = path.join(sessionDir, `${newSessionId}.json`);
301
- } else {
302
- return res.status(400).json({ error: 'Invalid toolType. Must be claude, codex, or gemini' });
303
- }
304
-
305
- // 确保目录存在
306
- if (!fs.existsSync(sessionDir)) {
307
- fs.mkdirSync(sessionDir, { recursive: true });
308
- }
309
-
310
- // 创建初始化会话文件
311
- const timestamp = new Date().toISOString();
312
- let initialContent;
313
-
314
- if (toolType === 'gemini') {
315
- // Gemini 使用 JSON 格式
316
- initialContent = JSON.stringify({
317
- id: newSessionId,
318
- projectPath: fullPath,
319
- createdAt: timestamp,
320
- messages: []
321
- }, null, 2);
322
- } else {
323
- // Claude 和 Codex 使用 JSONL 格式
324
- const metadata = {
325
- type: 'metadata',
326
- cwd: fullPath,
327
- gitBranch: null,
328
- timestamp: timestamp
329
- };
330
- initialContent = JSON.stringify(metadata) + '\n';
331
- }
332
-
333
- fs.writeFileSync(sessionFile, initialContent, 'utf8');
334
-
335
- // 广播日志
336
- broadcastLog({
337
- type: 'action',
338
- action: 'create_session',
339
- message: `创建新会话: ${newSessionId.substring(0, 8)} (${toolType})`,
340
- sessionId: newSessionId,
341
- tool: toolType,
342
- timestamp: Date.now()
343
- });
344
-
345
- const snapshotSource = toolType === 'codex' || toolType === 'gemini' ? toolType : 'claude';
346
- invalidateSessionSnapshots(snapshotSource, projectName);
347
- invalidateProjectSnapshots(snapshotSource);
348
-
349
- res.json({
350
- success: true,
351
- sessionId: newSessionId,
352
- sessionFile,
353
- toolType,
354
- projectName
355
- });
356
- } catch (error) {
357
- console.error('Error creating session:', error);
358
- res.status(500).json({ error: error.message });
359
- }
360
- });
361
-
362
- // POST /api/sessions/:projectName/order - Save session order
363
- router.post('/:projectName/order', (req, res) => {
364
- try {
365
- const { projectName } = req.params;
366
- const { order } = req.body;
367
- if (!Array.isArray(order)) {
368
- return res.status(400).json({ error: 'order must be an array' });
369
- }
370
- saveSessionOrder(projectName, order);
371
- invalidateSessionSnapshots('claude', projectName);
372
- res.json({ success: true });
373
- } catch (error) {
374
- console.error('Error saving session order:', error);
375
- res.status(500).json({ error: error.message });
376
- }
377
- });
378
-
379
- // GET /api/sessions/:projectName/search - Search sessions content
380
- router.get('/:projectName/search', async (req, res) => {
381
- try {
382
- const { projectName } = req.params;
383
- const { keyword, context } = req.query;
384
-
385
- if (!keyword) {
386
- return res.status(400).json({ error: 'Keyword is required' });
387
- }
388
-
389
- const contextLength = context ? parseInt(context) : 15;
390
- const results = await searchSessions(config, projectName, keyword, contextLength);
391
-
392
- res.json({
393
- keyword,
394
- totalMatches: results.reduce((sum, r) => sum + r.matchCount, 0),
395
- sessions: results
396
- });
397
- } catch (error) {
398
- console.error('Error searching sessions:', error);
399
- res.status(500).json({ error: error.message });
400
- }
401
- });
402
-
403
- // GET /api/sessions/:projectName/:sessionId/status - Lightweight status for live sync
404
- router.get('/:projectName/:sessionId/status', async (req, res) => {
405
- try {
406
-
407
- // Try session-history-index first for fast lookup
408
- try {
409
- const idxStatus = await getSessionStatus('claude', req.params.sessionId, { consistency: 'stale-ok' });
410
- if (idxStatus) {
411
- return res.json({
412
- sessionId: idxStatus.sessionId,
413
- lastModified: new Date(idxStatus.lastModified).toISOString(),
414
- size: idxStatus.size
415
- });
416
- }
417
- } catch (_) { /* fall through to direct stat */ }
418
-
419
- const { projectName, sessionId } = req.params;
420
- const { fullPath } = parseRealProjectPath(projectName);
421
- const { sessionFile, triedPaths } = resolveClaudeSessionFile(projectName, sessionId, fullPath);
422
-
423
- if (!sessionFile) {
424
- return res.status(404).json({
425
- error: `Session file not found: ${sessionId}`,
426
- triedPaths
427
- });
428
- }
429
-
430
- const stats = await fs.promises.stat(sessionFile);
431
- res.json({
432
- sessionId,
433
- lastModified: stats.mtime.toISOString(),
434
- size: stats.size
435
- });
436
- } catch (error) {
437
- console.error('Error fetching session status:', error);
438
- res.status(500).json({ error: error.message });
439
- }
440
- });
441
-
442
- // GET /api/sessions/:projectName/:sessionId/outline - Lightweight user outline for TOC and forking
443
- router.get('/:projectName/:sessionId/outline', async (req, res) => {
444
- try {
445
-
446
- // Try session-history-index first for fast lookup
447
- try {
448
- const idxOutline = await getSessionOutline('claude', req.params.sessionId, { consistency: 'stale-ok' });
449
- if (idxOutline) {
450
- return res.json({
451
- sessionId: idxOutline.sessionId,
452
- items: idxOutline.items
453
- });
454
- }
455
- } catch (_) { /* fall through to direct read */ }
456
-
457
- const { projectName, sessionId } = req.params;
458
- const { fullPath } = parseRealProjectPath(projectName);
459
- const { sessionFile, triedPaths } = resolveClaudeSessionFile(projectName, sessionId, fullPath);
460
-
461
- if (!sessionFile) {
462
- return res.status(404).json({
463
- error: `Session file not found: ${sessionId}`,
464
- triedPaths
465
- });
466
- }
467
-
468
- const outline = await readClaudeSessionOutline(sessionFile);
469
- res.json({
470
- sessionId,
471
- items: outline
472
- });
473
- } catch (error) {
474
- console.error('Error fetching session outline:', error);
475
- res.status(500).json({ error: error.message });
476
- }
477
- });
478
-
479
- // GET /api/sessions/:projectName/:sessionId/messages - Get session messages with pagination
480
- router.get('/:projectName/:sessionId/messages', async (req, res) => {
481
- try {
482
-
483
- // Try session-history-index first for fast lookup
484
- try {
485
- const { page = 1, limit = 20, order = 'desc' } = req.query;
486
- const pageNum = parseInt(page) || 1;
487
- const limitNum = parseInt(limit) || 20;
488
- const idxPage = await getMessagePage('claude', req.params.sessionId, {
489
- page: pageNum,
490
- limit: limitNum,
491
- order
492
- });
493
- if (idxPage) {
494
- return res.json(idxPage);
495
- }
496
- } catch (_) { /* fall through to direct file read */ }
497
-
498
- const { projectName, sessionId } = req.params;
499
- const { page = 1, limit = 20, order = 'desc' } = req.query;
500
-
501
- console.log(`[Messages API] Request for ${projectName}/${sessionId}, page=${page}, limit=${limit}`);
502
-
503
- const pageNum = parseInt(page);
504
- const limitNum = parseInt(limit);
505
-
506
- // Parse real project path
507
- const { fullPath } = parseRealProjectPath(projectName);
508
- console.log(`[Messages API] Parsed project path: ${fullPath}`);
509
-
510
- // Try to find session file
511
- const { sessionFile, triedPaths: possiblePaths } = resolveClaudeSessionFile(projectName, sessionId, fullPath);
512
-
513
- console.log(`[Messages API] Trying paths:`, possiblePaths);
514
-
515
- if (sessionFile) {
516
- console.log(`[Messages API] Found session file: ${sessionFile}`);
517
- }
518
-
519
- if (!sessionFile) {
520
- console.error(`[Messages API] Session file not found for: ${sessionId}`);
521
- return res.status(404).json({
522
- error: `Session file not found: ${sessionId}`,
523
- triedPaths: possiblePaths
524
- });
525
- }
526
-
527
- // Check if session has actual messages (not just file-history-snapshots)
528
- if (!hasActualMessages(sessionFile)) {
529
- console.warn(`[Messages API] Session ${sessionId} has no actual messages (only file-history-snapshots)`);
530
- return res.status(404).json({
531
- error: `Session has no conversation messages: ${sessionId}`,
532
- reason: 'This session contains only file history snapshots, not actual conversation data'
533
- });
534
- }
535
-
536
- // Read and parse session file
537
- const allMessages = [];
538
- const metadata = {};
539
-
540
- const stream = fs.createReadStream(sessionFile, { encoding: 'utf8' });
541
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
542
- let lastAssistantModel = null;
543
- let userMessageNumber = 0;
544
-
545
- try {
546
- for await (const line of rl) {
547
- if (!line.trim()) continue;
548
- try {
549
- const json = JSON.parse(line);
550
-
551
- if (json.type === 'summary' && json.summary) {
552
- metadata.summary = json.summary;
553
- }
554
- if (json.gitBranch) {
555
- metadata.gitBranch = json.gitBranch;
556
- }
557
- if (json.cwd) {
558
- metadata.cwd = json.cwd;
559
- }
560
-
561
- if (json.type === 'user' || json.type === 'assistant') {
562
- const resolvedModel = json.message?.model || json.model || lastAssistantModel || null;
563
- const messageId = json.message?.id || json.uuid || null;
564
- const message = {
565
- type: json.type,
566
- content: null,
567
- timestamp: json.timestamp || null,
568
- model: resolvedModel,
569
- messageId,
570
- subtype: null
571
- };
572
- let deferredToolResultContent = '';
573
-
574
- if (json.type === 'user') {
575
- const normalizedUserText = getClaudeUserText(json.message?.content);
576
- if (typeof json.message?.content === 'string') {
577
- message.content = json.message.content;
578
- } else if (Array.isArray(json.message?.content)) {
579
- const userParts = [];
580
- const toolResultParts = [];
581
- for (const item of json.message.content) {
582
- if (item.type === 'text' && item.text) {
583
- userParts.push(item.text);
584
- } else if (item.type === 'tool_result') {
585
- const resultContent = typeof item.content === 'string'
586
- ? item.content
587
- : JSON.stringify(item.content, null, 2);
588
- toolResultParts.push(`**[工具结果]**\n\`\`\`\n${resultContent}\n\`\`\``);
589
- } else if (item.type === 'image') {
590
- userParts.push('[图片]');
591
- }
592
- }
593
-
594
- if (userParts.length > 0) {
595
- message.content = userParts.join('\n\n');
596
- }
597
-
598
- // Claude tool_result is carried in a "user" envelope, but should be rendered as AI tool output.
599
- if (toolResultParts.length > 0) {
600
- deferredToolResultContent = toolResultParts.join('\n\n');
601
- }
602
- }
603
-
604
- if (normalizedUserText) {
605
- userMessageNumber += 1;
606
- message.userMessageNumber = userMessageNumber;
607
- }
608
- } else if (json.type === 'assistant') {
609
- if (Array.isArray(json.message?.content)) {
610
- const parts = [];
611
- for (const item of json.message.content) {
612
- if (item.type === 'text' && item.text) {
613
- parts.push(item.text);
614
- } else if (item.type === 'tool_use') {
615
- const inputStr = JSON.stringify(item.input, null, 2);
616
- parts.push(`**[调用工具: ${item.name}]**\n\`\`\`json\n${inputStr}\n\`\`\``);
617
- } else if (item.type === 'thinking' && item.thinking) {
618
- parts.push(`**[思考]**\n${item.thinking}`);
619
- }
620
- }
621
- message.content = parts.join('\n\n') || '[处理中...]';
622
- } else if (typeof json.message?.content === 'string') {
623
- message.content = json.message.content;
624
- }
625
-
626
- if (message.model) {
627
- lastAssistantModel = message.model;
628
- }
629
- }
630
-
631
- if (message.content && message.content !== 'Warmup') {
632
- allMessages.push(message);
633
- }
634
-
635
- if (deferredToolResultContent) {
636
- allMessages.push({
637
- type: 'assistant',
638
- subtype: 'tool_result',
639
- content: deferredToolResultContent,
640
- timestamp: json.timestamp || null,
641
- model: resolvedModel,
642
- messageId: messageId ? `${messageId}-tool-result` : null
643
- });
644
- }
645
- }
646
- } catch (err) {
647
- // Skip invalid lines
648
- }
649
- }
650
- } finally {
651
- rl.close();
652
- stream.destroy();
653
- }
654
-
655
- // Sort messages (desc = newest first)
656
- if (order === 'desc') {
657
- allMessages.reverse();
658
- }
659
-
660
- console.log(`[Messages API] Parsed ${allMessages.length} total messages`);
661
-
662
- // Pagination
663
- const total = allMessages.length;
664
- const startIndex = (pageNum - 1) * limitNum;
665
- const endIndex = startIndex + limitNum;
666
- const messages = allMessages.slice(startIndex, endIndex);
667
- const hasMore = endIndex < total;
668
-
669
- console.log(`[Messages API] Returning ${messages.length} messages (page ${pageNum}, total ${total})`);
670
-
671
- res.json({
672
- messages,
673
- metadata,
674
- pagination: {
675
- page: pageNum,
676
- limit: limitNum,
677
- total,
678
- hasMore
679
- }
680
- });
681
- } catch (error) {
682
- console.error('Error fetching session messages:', error);
683
- res.status(500).json({ error: error.message });
684
- }
685
- });
686
-
687
- // POST /api/sessions/:projectName/:sessionId/launch - Return session launch command for copy
688
- router.post('/:projectName/:sessionId/launch', async (req, res) => {
689
- try {
690
- const { projectName, sessionId } = req.params;
691
- const path = require('path');
692
- const fs = require('fs');
693
-
694
- // Parse real project path (important for cross-project sessions)
695
- const { fullPath } = parseRealProjectPath(projectName);
696
-
697
- const projectSessionsDir = path.join(fullPath, '.claude', 'sessions');
698
- const projectSessionFile = path.join(projectSessionsDir, sessionId + '.jsonl');
699
-
700
- // Try to find session file in multiple possible locations
701
- let sessionFile = null;
702
- const possiblePaths = [
703
- projectSessionFile,
704
- // Location 2: User's .claude/projects directory (ClaudeCode default)
705
- path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
706
- ];
707
-
708
- for (const testPath of possiblePaths) {
709
- if (fs.existsSync(testPath)) {
710
- sessionFile = testPath;
711
- break;
712
- }
713
- }
714
-
715
- // 如果会话只存在于全局目录,则复制到项目的 .claude/sessions 目录,避免 claude -r 找不到文件
716
- if (sessionFile && sessionFile !== projectSessionFile) {
717
- try {
718
- if (!fs.existsSync(projectSessionsDir)) {
719
- fs.mkdirSync(projectSessionsDir, { recursive: true });
720
- }
721
- fs.copyFileSync(sessionFile, projectSessionFile);
722
- sessionFile = projectSessionFile;
723
- } catch (copyError) {
724
- console.warn('Failed to sync session file to project directory:', copyError.message);
725
- }
726
- }
727
-
728
- if (!sessionFile) {
729
- console.error(`Session file not found in any location for session: ${sessionId}`);
730
- console.error('Tried paths:', possiblePaths);
731
- return res.status(404).json({
732
- error: `No conversation found with session ID: ${sessionId}`,
733
- details: `Tried locations: ${possiblePaths.join(', ')}`
734
- });
735
- }
736
-
737
- // Extract working directory from session file
738
- let cwd = fullPath; // Default to project directory
739
- try {
740
- const content = fs.readFileSync(sessionFile, 'utf8');
741
- const firstLine = content.split('\n')[0];
742
- if (firstLine) {
743
- const json = JSON.parse(firstLine);
744
- if (json.cwd) {
745
- cwd = json.cwd;
746
- }
747
- }
748
- } catch (e) {
749
- console.warn('Unable to extract cwd from session, using project path:', e.message);
750
- }
751
-
752
- // 确保会话文件在 cwd 的 .claude/sessions/ 目录下
753
- // 这样 claude -r 才能找到文件
754
- const cwdSessionsDir = path.join(cwd, '.claude', 'sessions');
755
- const cwdSessionFile = path.join(cwdSessionsDir, sessionId + '.jsonl');
756
-
757
- // 如果会话文件不在 cwd 的 sessions 目录,复制过去
758
- if (sessionFile !== cwdSessionFile && !fs.existsSync(cwdSessionFile)) {
759
- try {
760
- if (!fs.existsSync(cwdSessionsDir)) {
761
- fs.mkdirSync(cwdSessionsDir, { recursive: true });
762
- }
763
- fs.copyFileSync(sessionFile, cwdSessionFile);
764
- console.log(`[Launch] Copied session to cwd: ${cwdSessionFile}`);
765
- } catch (copyError) {
766
- console.warn('[Launch] Failed to copy session file to cwd:', copyError.message);
767
- // 如果复制失败,尝试更新 cwd 为项目目录
768
- if (fs.existsSync(projectSessionsDir)) {
769
- cwd = fullPath;
770
- console.log(`[Launch] Fallback to project directory: ${cwd}`);
771
- }
772
- }
773
- }
774
-
775
- // Get alias
776
- const aliases = loadAliases();
777
- const alias = aliases[sessionId];
778
-
779
- // 广播行为日志
780
- broadcastLog({
781
- type: 'action',
782
- action: 'launch_session',
783
- message: `复制会话启动命令 ${alias || sessionId.substring(0, 8)} (claude)`,
784
- sessionId,
785
- alias: alias || null,
786
- tool: 'claude',
787
- timestamp: Date.now()
788
- });
789
-
790
- const command = `claude -r ${sessionId}`;
791
- const quotedCwd = `"${String(cwd).replace(/"/g, '\\"')}"`;
792
- const copyCommand = `cd ${quotedCwd} && ${command}`;
793
-
794
- res.json({
795
- success: true,
796
- cwd,
797
- sessionFile,
798
- sessionId,
799
- tool: 'claude',
800
- command,
801
- copyCommand
802
- });
803
- } catch (error) {
804
- console.error('Error preparing launch command:', error);
805
- res.status(500).json({ error: error.message });
806
- }
807
- });
808
-
809
- return router;
810
- };