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
@@ -1,14 +1,40 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { PATHS, HOME_DIR } = require('../../config/paths');
3
+ const { PATHS, HOME_DIR } = require('../../../config/paths');
4
+ const ompConfig = require('./config');
4
5
  const {
5
- listProjects: idxListProjects,
6
- listSessions: idxListSessions,
7
- getRecentSessions: idxGetRecent,
8
- searchSessions: idxSearch,
9
- getSessionStatus: idxGetSessionStatus,
10
- } = require('./session-history-index');
11
- const { getOmpCommand, getOmpPaths, resolveOmpRuntime } = require('./omp-config');
6
+ normalizeUsage: normalizeNativeUsage,
7
+ readJsonLines: readNativeJsonLines,
8
+ usageFieldPaths,
9
+ createSelectiveJsonLineParser,
10
+ createIncrementalJsonlCursor
11
+ } = require('../native-log-utils');
12
+ let sessionHistoryIndex = null;
13
+ let ompSessionPathsOverride = null;
14
+
15
+ function configure({ sessionHistoryIndex: index, pathContext } = {}) {
16
+ sessionHistoryIndex = index || null;
17
+ ompConfig.configure?.({ pathContext });
18
+ const native = pathContext?.customized ? (pathContext.native || {}) : {};
19
+ ompSessionPathsOverride = native.sessions
20
+ ? { ...native, agentDir: native.dir || path.dirname(native.sessions) }
21
+ : null;
22
+ }
23
+
24
+ function getSessionHistoryIndex(options = {}) {
25
+ if (!sessionHistoryIndex) {
26
+ const { getDefaultDependencies } = require('../../../platforms/runtime');
27
+ sessionHistoryIndex = getDefaultDependencies().sessionHistoryIndex;
28
+ if (!sessionHistoryIndex) {
29
+ const indexModule = require('../../../server/services/session-history-index');
30
+ sessionHistoryIndex = Object.keys(options).length > 0
31
+ ? indexModule.createSessionHistoryIndex({ config: options })
32
+ : indexModule;
33
+ }
34
+ }
35
+ return sessionHistoryIndex;
36
+ }
37
+ const { getOmpCommand, getOmpPaths, resolveOmpRuntime } = require('./config');
12
38
 
13
39
  const PROJECT_ORDER_FILE = PATHS.ompProjectOrder;
14
40
  const SESSION_ORDER_FILE = PATHS.ompSessionOrder;
@@ -116,34 +142,24 @@ function extractMessageText(message = {}) {
116
142
  }
117
143
 
118
144
  function parseUsage(usage = {}) {
119
- const input = Number(usage.input ?? usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? usage.promptTokens ?? 0) || 0;
120
- const output = Number(usage.output ?? usage.outputTokens ?? usage.output_tokens ?? usage.completion_tokens ?? usage.completionTokens ?? 0) || 0;
121
- const cacheRead = Number(usage.cacheRead ?? usage.cache_read ?? usage.cachedTokens ?? usage.cached_tokens ?? usage.cache_read_input_tokens ?? 0) || 0;
122
- const cacheWrite = Number(usage.cacheWrite ?? usage.cache_write ?? usage.cacheCreation ?? usage.cache_creation ?? usage.cache_creation_input_tokens ?? 0) || 0;
123
- const reasoning = Number(usage.reasoningTokens ?? usage.reasoning_tokens ?? 0) || 0;
124
- const total = Number(usage.totalTokens ?? usage.total_tokens ?? (input + output + cacheRead + cacheWrite + reasoning)) || 0;
145
+ const normalized = normalizeNativeUsage(usage);
146
+ const input = normalized.input;
147
+ const output = normalized.output;
148
+ const cacheRead = normalized.cacheRead;
149
+ const cacheWrite = normalized.cacheCreation;
150
+ const reasoning = normalized.reasoning;
151
+ const total = normalized.total;
125
152
  const cost = typeof usage.cost === 'number'
126
153
  ? usage.cost
127
154
  : Number(usage.cost?.total ?? usage.cost?.usd ?? 0) || 0;
128
- return { input, output, cached: cacheRead, cacheRead, cacheWrite, reasoning, total, cost };
155
+ return {
156
+ input, output, cached: normalized.cached, cacheRead, cacheWrite,
157
+ cacheCreation: normalized.cacheCreation, reasoning, total, cost
158
+ };
129
159
  }
130
160
 
131
161
  function readJsonLines(filePath) {
132
- try {
133
- const raw = fs.readFileSync(filePath, 'utf8');
134
- return raw
135
- .split(/\r?\n/)
136
- .filter(line => line.trim())
137
- .map((line) => {
138
- try {
139
- return JSON.parse(line);
140
- } catch {
141
- return { type: 'invalid', raw: line };
142
- }
143
- });
144
- } catch {
145
- return [];
146
- }
162
+ return readNativeJsonLines(filePath, fs);
147
163
  }
148
164
 
149
165
  function convertOmpEntry(entry = {}, index = 0) {
@@ -219,43 +235,64 @@ function parseSessionFile(filePath) {
219
235
  };
220
236
  }
221
237
 
222
- function parseSessionUsageEvents(filePath) {
223
- const entries = readJsonLines(filePath);
238
+ function createSessionUsageParserState(filePath, entries = [], previousState = {}) {
224
239
  const header = entries.find(entry => entry?.type === 'session') || {};
225
- const sessionId = header.id || parseOmpSessionId(filePath);
226
- let activeProvider = '';
227
- let activeModel = '';
228
- const events = [];
240
+ return {
241
+ sessionId: previousState.sessionId || header.id || parseOmpSessionId(filePath),
242
+ sessionTimestamp: previousState.sessionTimestamp ?? header.timestamp ?? null,
243
+ activeProvider: previousState.activeProvider || '',
244
+ activeModel: previousState.activeModel || '',
245
+ entryIndex: Number.isInteger(previousState.entryIndex) ? previousState.entryIndex : 0
246
+ };
247
+ }
229
248
 
230
- entries.forEach((entry, index) => {
231
- if (entry?.type === 'model_change') {
232
- activeProvider = entry.provider || activeProvider;
233
- activeModel = entry.modelId || entry.model || activeModel;
234
- return;
235
- }
236
- if (entry?.type !== 'message') return;
249
+ function parseSessionUsageEntry(filePath, entry, index, state) {
250
+ if (entry?.type === 'session') {
251
+ state.sessionId = entry.id || state.sessionId;
252
+ state.sessionTimestamp = entry.timestamp || state.sessionTimestamp;
253
+ return null;
254
+ }
255
+ if (entry?.type === 'model_change') {
256
+ state.activeProvider = entry.provider || state.activeProvider;
257
+ state.activeModel = entry.modelId || entry.model || state.activeModel;
258
+ return null;
259
+ }
260
+ if (entry?.type !== 'message') return null;
237
261
 
238
- const message = entry.message || {};
239
- const role = message.role || entry.role;
240
- if (role !== 'assistant') return;
262
+ const message = entry.message || {};
263
+ const role = message.role || entry.role;
264
+ if (role !== 'assistant') return null;
241
265
 
242
- const eventId = entry.id || message.id || `assistant-${index}`;
243
- events.push({
244
- key: `${filePath}:${eventId}`,
245
- id: `${sessionId}:${eventId}`,
246
- sessionId,
247
- filePath,
248
- provider: entry.provider || message.provider || activeProvider || '',
249
- model: entry.model || message.model || activeModel || '',
250
- timestamp: entry.timestamp || message.timestamp || header.timestamp || null,
251
- usage: parseUsage(entry.usage || message.usage || {})
252
- });
266
+ const eventId = entry.id || message.id || `assistant-${index}`;
267
+ return {
268
+ key: `${filePath}:${eventId}`,
269
+ id: `${state.sessionId}:${eventId}`,
270
+ sessionId: state.sessionId,
271
+ filePath,
272
+ provider: entry.provider || message.provider || state.activeProvider || '',
273
+ model: entry.model || message.model || state.activeModel || '',
274
+ timestamp: entry.timestamp || message.timestamp || state.sessionTimestamp || null,
275
+ usage: parseUsage(entry.usage || message.usage || {})
276
+ };
277
+ }
278
+
279
+ function parseSessionUsageEntries(filePath, entries, previousState = {}) {
280
+ const state = createSessionUsageParserState(filePath, entries, previousState);
281
+ const events = [];
282
+ entries.forEach((entry, index) => {
283
+ const event = parseSessionUsageEntry(filePath, entry, index, state);
284
+ if (event) events.push(event);
253
285
  });
286
+ return { events, state };
287
+ }
254
288
 
255
- return events;
289
+ function parseSessionUsageEvents(filePath) {
290
+ const entries = readJsonLines(filePath);
291
+ return parseSessionUsageEntries(filePath, entries).events;
256
292
  }
257
293
 
258
294
  function getOmpSessionPaths() {
295
+ if (ompSessionPathsOverride?.sessions) return ompSessionPathsOverride;
259
296
  // Reading historical sessions normally only needs OMP's native directory
260
297
  // convention. Avoid starting the CLI here: project/count snapshot workers
261
298
  // invoke this path independently, which made Windows repeatedly open
@@ -294,8 +331,8 @@ function scanSessionFiles(rootDir = getOmpSessionPaths().sessions) {
294
331
  }
295
332
 
296
333
  async function getAllSessions(options = {}) {
297
- const projects = await idxListProjects('omp', options);
298
- const groups = await Promise.all(projects.map(project => idxListSessions('omp', project.name, options)));
334
+ const projects = await getSessionHistoryIndex().listProjects('omp', options);
335
+ const groups = await Promise.all(projects.map(project => getSessionHistoryIndex().listSessions('omp', project.name, options)));
299
336
  return groups.flat().map(session => normalizeSession({
300
337
  ...session,
301
338
  mtime: new Date(session.mtime).toISOString(),
@@ -316,34 +353,31 @@ function getOmpUsageEvents(rootDir = getOmpSessionPaths().sessions) {
316
353
  });
317
354
  }
318
355
 
319
- function createOmpUsageEventCursor(rootDir = null) {
320
- let signatures = new Map();
356
+ const OMP_SELECTED_FIELDS = [
357
+ 'type', 'id', 'timestamp', 'provider', 'model', 'modelId', 'cwd',
358
+ 'role', 'message.role', 'message.id', 'message.timestamp', 'message.provider',
359
+ 'message.model', 'message.modelId',
360
+ ...usageFieldPaths('usage'),
361
+ ...usageFieldPaths('message.usage')
362
+ ];
321
363
 
322
- return {
323
- read() {
324
- const files = scanSessionFiles(rootDir || getOmpSessionPaths().sessions);
325
- const nextSignatures = new Map();
326
- const events = [];
327
-
328
- files.forEach((filePath) => {
329
- try {
330
- const stat = fs.statSync(filePath);
331
- const signature = `${stat.size}:${stat.mtimeMs}`;
332
- nextSignatures.set(filePath, signature);
333
- if (signatures.get(filePath) === signature) return;
334
- events.push(...parseSessionUsageEvents(filePath));
335
- } catch (error) {
336
- console.warn('[OMP Sessions] Failed to read changed usage events:', filePath, error.message);
337
- }
338
- });
339
-
340
- signatures = nextSignatures;
341
- return events;
342
- },
343
- reset() {
344
- signatures = new Map();
364
+ function createOmpUsageEventCursor(rootDir = null, { skipInitialParse = false, onDiagnostic } = {}) {
365
+ return createIncrementalJsonlCursor({
366
+ scanFiles: () => scanSessionFiles(rootDir || getOmpSessionPaths().sessions),
367
+ createFileState: (filePath, _stat, previousState) => createSessionUsageParserState(
368
+ filePath,
369
+ [],
370
+ previousState || {}
371
+ ),
372
+ parseLine: (filePath, record, state, index) => parseSessionUsageEntry(filePath, record, index, state),
373
+ fsImpl: fs,
374
+ skipInitialParse,
375
+ createLongLineParser: () => createSelectiveJsonLineParser(OMP_SELECTED_FIELDS),
376
+ onDiagnostic,
377
+ onError: (error, filePath) => {
378
+ console.warn('[OMP Sessions] Failed to read changed usage events:', filePath, error.message);
345
379
  }
346
- };
380
+ });
347
381
  }
348
382
 
349
383
  function loadProjectOrder() {
@@ -355,8 +389,8 @@ function loadSessionOrder() {
355
389
  return safeReadJson(SESSION_ORDER_FILE, {});
356
390
  }
357
391
 
358
- async function getProjects(_options = {}) {
359
- const idxProjects = await idxListProjects('omp');
392
+ async function getProjects(options = {}) {
393
+ const idxProjects = await getSessionHistoryIndex().listProjects('omp', options);
360
394
  const projects = idxProjects.map(p => ({
361
395
  name: p.name,
362
396
  path: p.fullPath || p.path || '',
@@ -383,8 +417,8 @@ async function getProjects(_options = {}) {
383
417
  return projects;
384
418
  }
385
419
 
386
- async function getSessionsByProject(projectName, _options = {}) {
387
- const idxSessions = await idxListSessions('omp', projectName);
420
+ async function getSessionsByProject(projectName, options = {}) {
421
+ const idxSessions = await getSessionHistoryIndex().listSessions('omp', projectName, options);
388
422
  const sessions = idxSessions.map(s => ({
389
423
  sessionId: s.sessionId,
390
424
  filePath: s.filePath,
@@ -437,7 +471,7 @@ function normalizeSession(session) {
437
471
  }
438
472
 
439
473
  async function getSessionById(sessionId) {
440
- const status = await idxGetSessionStatus('omp', sessionId);
474
+ const status = await getSessionHistoryIndex().getSessionStatus('omp', sessionId);
441
475
  if (!status) return null;
442
476
  return {
443
477
  sessionId: status.sessionId,
@@ -465,7 +499,7 @@ async function getSessionMessages(sessionId) {
465
499
  }
466
500
 
467
501
  async function getRecentSessions(limit = 5) {
468
- const indexed = await idxGetRecent('omp', limit);
502
+ const indexed = await getSessionHistoryIndex().getRecentSessions('omp', limit);
469
503
  return indexed.map(session => normalizeSession({
470
504
  ...session,
471
505
  mtime: new Date(session.mtime).toISOString(),
@@ -476,7 +510,7 @@ async function getRecentSessions(limit = 5) {
476
510
  }
477
511
 
478
512
  async function searchSessions(keyword, contextLength = 35, projectName = null) {
479
- return idxSearch('omp', keyword, { contextLength, projectName });
513
+ return getSessionHistoryIndex().searchSessions('omp', keyword, { contextLength, projectName });
480
514
  }
481
515
 
482
516
  async function deleteSession(sessionId) {
@@ -514,7 +548,7 @@ function saveSessionOrder(projectName, order) {
514
548
  }
515
549
 
516
550
  async function getProjectAndSessionCounts(options = {}) {
517
- const projects = await idxListProjects('omp', options);
551
+ const projects = await getSessionHistoryIndex().listProjects('omp', options);
518
552
  return {
519
553
  projectCount: projects.length,
520
554
  sessionCount: projects.reduce((sum, project) => sum + (project.sessionCount || 0), 0)
@@ -564,6 +598,7 @@ function isOmpCliInstalled() {
564
598
  }
565
599
 
566
600
  module.exports = {
601
+ configure,
567
602
  buildLaunchCommand,
568
603
  createOmpUsageEventCursor,
569
604
  decodeProjectName,
@@ -573,6 +608,7 @@ module.exports = {
573
608
  forkSession,
574
609
  getAllSessions,
575
610
  getOmpUsageEvents,
611
+ getOmpSessionPaths,
576
612
  getProjectAndSessionCounts,
577
613
  getProjects,
578
614
  getRecentSessions,
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const { createCapabilityDriver } = require('../../../shared/capability-driver');
4
+
5
+ function createDriver(context = {}) {
6
+ return createCapabilityDriver({
7
+ ...context,
8
+ platform: 'omp',
9
+ capability: 'sessions',
10
+ parserVersion: 2,
11
+ servicePath: './omp/sessions-implementation',
12
+ localServicePath: '../platforms/drivers/omp/sessions-implementation',
13
+ adapterLocalPath: '../platforms/drivers/omp/session-history-adapter',
14
+ adapterMethods: { inventory: 'inventory', parse: 'parse' },
15
+ methods: {
16
+ getProjects: 'getProjects',
17
+ listSessions: 'getSessionsByProject',
18
+ recent: 'getRecentSessions',
19
+ search: 'searchSessions',
20
+ getSessionById: 'getSessionById',
21
+ messages: 'getSessionMessages',
22
+ delete: 'deleteSession',
23
+ fork: 'forkSession',
24
+ saveSessionOrder: 'saveSessionOrder',
25
+ buildLaunchCommand: 'buildLaunchCommand',
26
+ status: 'getSessionStatus'
27
+ },
28
+ onSuccess: operation => {
29
+ if (['delete', 'fork', 'saveSessionOrder'].includes(operation)) {
30
+ context.sessionHistoryIndex?.invalidateSource('omp');
31
+ }
32
+ }
33
+ });
34
+ }
35
+
36
+ module.exports = { createDriver };
@@ -5,8 +5,9 @@ const {
5
5
  getOmpCommand,
6
6
  getOmpPaths,
7
7
  readOmpSettings
8
- } = require('./omp-config');
9
- const { HOME_DIR, NATIVE_PATHS } = require('../../config/paths');
8
+ } = require('./config');
9
+ const { HOME_DIR, NATIVE_PATHS } = require('../../../config/paths');
10
+ const { assertNoSymlinkComponents } = require('../../../shared/project-config');
10
11
 
11
12
  const DEFAULT_SKILL_SETTINGS = Object.freeze({
12
13
  enabled: true,
@@ -23,6 +24,31 @@ const DEFAULT_SKILL_SETTINGS = Object.freeze({
23
24
  disabledExtensions: []
24
25
  });
25
26
 
27
+ const DISCOVERY_CACHE_TTL_MS = 1000;
28
+ const settingsCache = new Map();
29
+ const pluginPathsCache = new Map();
30
+ const discoveryCache = new Map();
31
+
32
+ function canonicalCwd(cwd) {
33
+ if (!cwd) return '';
34
+ const resolved = path.resolve(cwd);
35
+ try {
36
+ return fs.realpathSync(resolved);
37
+ } catch {
38
+ return resolved;
39
+ }
40
+ }
41
+
42
+ function cacheKey(cwd, scope = 'user') {
43
+ return `${scope}:${canonicalCwd(cwd)}`;
44
+ }
45
+
46
+ function cloneSkills(skills) {
47
+ return Array.isArray(skills)
48
+ ? skills.map(skill => ({ ...skill, shadowedSources: Array.isArray(skill.shadowedSources) ? skill.shadowedSources.map(source => ({ ...source })) : [] }))
49
+ : [];
50
+ }
51
+
26
52
  function expandHome(value) {
27
53
  const input = String(value || '').trim();
28
54
  if (input === '~') return HOME_DIR;
@@ -30,7 +56,7 @@ function expandHome(value) {
30
56
  return input;
31
57
  }
32
58
 
33
- function readEffectiveSettings(cwd) {
59
+ function _readEffectiveSettings(cwd) {
34
60
  try {
35
61
  const output = execFileSync(getOmpCommand(), ['config', 'list', '--json'], {
36
62
  cwd: cwd || process.cwd(),
@@ -60,6 +86,18 @@ function readEffectiveSettings(cwd) {
60
86
  }
61
87
  }
62
88
 
89
+ function readEffectiveSettings(cwd, { force = false, scope = 'user' } = {}) {
90
+ const key = cacheKey(cwd, scope);
91
+ const now = Date.now();
92
+ const cached = settingsCache.get(key);
93
+ if (!force && cached && now - cached.checkedAt < DISCOVERY_CACHE_TTL_MS) {
94
+ return { ...cached.value };
95
+ }
96
+ const value = _readEffectiveSettings(cwd);
97
+ settingsCache.set(key, { value, checkedAt: now });
98
+ return { ...value };
99
+ }
100
+
63
101
  function globMatches(value, pattern) {
64
102
  const escaped = String(pattern || '')
65
103
  .replace(/[.+^${}()|[\]\\]/g, '\\$&')
@@ -84,6 +122,7 @@ function isIncluded(name, settings) {
84
122
  function parseSkill(service, skillFile, provider, scope, requireDescription) {
85
123
  let metadata;
86
124
  try {
125
+ if (fs.lstatSync(skillFile).isSymbolicLink()) return null;
87
126
  metadata = service.parseSkillMd(fs.readFileSync(skillFile, 'utf8'));
88
127
  } catch {
89
128
  return null;
@@ -104,6 +143,9 @@ function parseSkill(service, skillFile, provider, scope, requireDescription) {
104
143
  const native = provider === 'native';
105
144
  return {
106
145
  key: `omp:${provider}:${scope}:${realPath}`,
146
+ sourceKey: native
147
+ ? `native:omp:${directory}`
148
+ : `omp:${provider}:${directory}`,
107
149
  name,
108
150
  description,
109
151
  directory,
@@ -125,8 +167,29 @@ function parseSkill(service, skillFile, provider, scope, requireDescription) {
125
167
  };
126
168
  }
127
169
 
128
- function scanOneLevel(service, root, descriptor, settings) {
170
+ function isSafeProjectScanRoot(root, projectRoot) {
171
+ if (!root || !projectRoot) return false;
172
+ const requestedRoot = path.resolve(root);
173
+ const requestedProjectRoot = path.resolve(projectRoot);
174
+ try {
175
+ const projectStat = fs.lstatSync(requestedProjectRoot);
176
+ const rootStat = fs.lstatSync(requestedRoot);
177
+ if (projectStat.isSymbolicLink() || rootStat.isSymbolicLink()) return false;
178
+ if (!projectStat.isDirectory() || !rootStat.isDirectory()) return false;
179
+ const resolvedProjectRoot = fs.realpathSync(requestedProjectRoot);
180
+ const resolvedRoot = fs.realpathSync(requestedRoot);
181
+ const relative = path.relative(resolvedProjectRoot, resolvedRoot);
182
+ if (relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) return false;
183
+ assertNoSymlinkComponents(resolvedProjectRoot, resolvedRoot, fs);
184
+ return true;
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+
190
+ function scanOneLevel(service, root, descriptor, settings, projectRoot = null) {
129
191
  if (!root || !fs.existsSync(root)) return [];
192
+ if (descriptor.scope === 'project' && !isSafeProjectScanRoot(root, projectRoot)) return [];
130
193
  const result = [];
131
194
  let entries = [];
132
195
  try {
@@ -136,15 +199,8 @@ function scanOneLevel(service, root, descriptor, settings) {
136
199
  }
137
200
 
138
201
  for (const entry of entries) {
202
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
139
203
  const entryPath = path.join(root, entry.name);
140
- if (!entry.isDirectory()) {
141
- if (!entry.isSymbolicLink()) continue;
142
- try {
143
- if (!fs.statSync(entryPath).isDirectory()) continue;
144
- } catch {
145
- continue;
146
- }
147
- }
148
204
  const skillFile = path.join(entryPath, 'SKILL.md');
149
205
  if (!fs.existsSync(skillFile)) continue;
150
206
  const skill = parseSkill(
@@ -161,7 +217,7 @@ function scanOneLevel(service, root, descriptor, settings) {
161
217
  return result;
162
218
  }
163
219
 
164
- function getPluginInstallPaths(cwd) {
220
+ function _getPluginInstallPaths(cwd) {
165
221
  try {
166
222
  const output = execFileSync(getOmpCommand(), ['plugin', 'list', '--json'], {
167
223
  cwd: cwd || process.cwd(),
@@ -196,6 +252,18 @@ function getPluginInstallPaths(cwd) {
196
252
  }
197
253
  }
198
254
 
255
+ function getPluginInstallPaths(cwd, { force = false, scope = 'user' } = {}) {
256
+ const key = cacheKey(cwd, scope);
257
+ const now = Date.now();
258
+ const cached = pluginPathsCache.get(key);
259
+ if (!force && cached && now - cached.checkedAt < DISCOVERY_CACHE_TTL_MS) {
260
+ return cached.value.map(item => ({ ...item }));
261
+ }
262
+ const value = _getPluginInstallPaths(cwd);
263
+ pluginPathsCache.set(key, { value, checkedAt: now });
264
+ return value.map(item => ({ ...item }));
265
+ }
266
+
199
267
  function getClaudePluginSkillRoots() {
200
268
  const pluginsDir = NATIVE_PATHS.claude.plugins
201
269
  || path.join(NATIVE_PATHS.claude.dir, 'plugins');
@@ -266,37 +334,63 @@ function compactShadow(skill) {
266
334
  }
267
335
 
268
336
  function deduplicateDiscoveredSkills(skills) {
269
- const byName = new Map();
337
+ const byIdentity = new Map();
270
338
  const byRealPath = new Map();
271
339
  const result = [];
340
+ const priority = skill => skill.sourceScope === 'project' ? 2 : 1;
341
+ const identityFor = skill => String(
342
+ skill.sourceKey || `${skill.sourceProvider || 'unknown'}:${String(skill.name || '').toLowerCase()}`
343
+ );
272
344
 
273
345
  for (const skill of skills) {
274
- const nameKey = skill.name.toLowerCase();
346
+ const identity = identityFor(skill);
275
347
  const pathKey = skill.realPath || skill.sourcePath;
276
- const existing = byName.get(nameKey) || byRealPath.get(pathKey);
277
- if (existing) {
278
- existing.shadowedSources.push(compactShadow(skill));
348
+ const existing = byIdentity.get(identity) || byRealPath.get(pathKey);
349
+ if (!existing) {
350
+ byIdentity.set(identity, skill);
351
+ if (pathKey) byRealPath.set(pathKey, skill);
352
+ result.push(skill);
353
+ continue;
354
+ }
355
+
356
+ if (priority(skill) > priority(existing)) {
357
+ skill.shadowedSources.push(compactShadow(existing));
358
+ const index = result.indexOf(existing);
359
+ if (index >= 0) result[index] = skill;
360
+ for (const [key, value] of byIdentity) {
361
+ if (value === existing) byIdentity.delete(key);
362
+ }
363
+ for (const [key, value] of byRealPath) {
364
+ if (value === existing) byRealPath.delete(key);
365
+ }
366
+ byIdentity.set(identity, skill);
367
+ if (pathKey) byRealPath.set(pathKey, skill);
279
368
  continue;
280
369
  }
281
- byName.set(nameKey, skill);
282
- byRealPath.set(pathKey, skill);
283
- result.push(skill);
370
+
371
+ existing.shadowedSources.push(compactShadow(skill));
284
372
  }
285
373
 
286
374
  return result;
287
375
  }
288
376
 
289
- function discoverOmpSkills(service, options = {}) {
290
- const cwd = options.cwd ? path.resolve(options.cwd) : null;
291
- const settings = readEffectiveSettings(cwd);
377
+ function _discoverOmpSkills(service, options = {}, settings = readEffectiveSettings(options.cwd), pluginPaths = getPluginInstallPaths(options.cwd), claudePluginRoots = getClaudePluginSkillRoots()) {
378
+ const cwd = options.cwd ? canonicalCwd(options.cwd) : null;
292
379
  if (settings.enabled === false) return [];
293
380
 
294
- const ompPaths = getOmpPaths();
295
- const userHome = HOME_DIR;
381
+ const customNative = options.pathContext?.customized ? (options.pathContext.native || {}) : null;
382
+ const ompPaths = customNative
383
+ ? {
384
+ ...getOmpPaths(),
385
+ ...customNative,
386
+ agentDir: customNative.dir || options.pathContext.home
387
+ }
388
+ : getOmpPaths();
389
+ const userHome = options.pathContext?.customized ? options.pathContext.home : HOME_DIR;
296
390
  const descriptors = [
297
391
  { provider: 'native', scope: 'user', root: ompPaths.skills, requireDescription: true },
298
392
  ...(cwd ? [{ provider: 'native', scope: 'project', root: path.join(cwd, '.omp', 'skills'), requireDescription: true }] : []),
299
- ...getPluginInstallPaths(cwd)
393
+ ...pluginPaths
300
394
  .filter(item => cwd || item.scope !== 'project')
301
395
  .map(item => ({
302
396
  provider: 'omp-plugins',
@@ -306,7 +400,7 @@ function discoverOmpSkills(service, options = {}) {
306
400
  })),
307
401
  { provider: 'claude', scope: 'user', root: NATIVE_PATHS.claude.skills, requireDescription: false },
308
402
  ...(cwd ? [{ provider: 'claude', scope: 'project', root: path.join(cwd, '.claude', 'skills'), requireDescription: false }] : []),
309
- ...getClaudePluginSkillRoots()
403
+ ...claudePluginRoots
310
404
  .filter(item => cwd || item.scope !== 'project')
311
405
  .map(item => ({
312
406
  provider: 'claude-plugins',
@@ -329,7 +423,7 @@ function discoverOmpSkills(service, options = {}) {
329
423
  const discovered = [];
330
424
  for (const descriptor of descriptors) {
331
425
  if (!providerEnabled(settings, descriptor.provider, descriptor.scope)) continue;
332
- discovered.push(...scanOneLevel(service, descriptor.root, descriptor, settings));
426
+ discovered.push(...scanOneLevel(service, descriptor.root, descriptor, settings, cwd));
333
427
  }
334
428
 
335
429
  const customDirectories = Array.isArray(settings.customDirectories)
@@ -338,18 +432,51 @@ function discoverOmpSkills(service, options = {}) {
338
432
  for (const customDirectory of customDirectories) {
339
433
  const expanded = expandHome(customDirectory);
340
434
  const root = path.isAbsolute(expanded)
341
- ? expanded
435
+ ? path.resolve(expanded)
342
436
  : path.resolve(cwd || process.cwd(), expanded);
437
+ const relative = cwd ? path.relative(cwd, root) : '..';
438
+ const isProjectRoot = Boolean(
439
+ cwd
440
+ && relative !== '..'
441
+ && !relative.startsWith(`..${path.sep}`)
442
+ && !path.isAbsolute(relative)
443
+ );
343
444
  discovered.push(...scanOneLevel(service, root, {
344
445
  provider: 'custom',
345
- scope: cwd && root.startsWith(`${cwd}${path.sep}`) ? 'project' : 'user',
446
+ scope: isProjectRoot ? 'project' : 'user',
346
447
  requireDescription: true
347
- }, settings));
448
+ }, settings, isProjectRoot ? cwd : null));
348
449
  }
349
450
 
350
451
  return deduplicateDiscoveredSkills(discovered);
351
452
  }
352
453
 
454
+ function discoverOmpSkills(service, options = {}) {
455
+ const scope = options.scope || 'user';
456
+ const cwd = options.cwd ? canonicalCwd(options.cwd) : null;
457
+ const settings = readEffectiveSettings(cwd, { force: options.force === true, scope });
458
+ if (settings.enabled === false) return [];
459
+ const pluginPaths = getPluginInstallPaths(cwd, { force: options.force === true, scope });
460
+ const claudePluginRoots = getClaudePluginSkillRoots();
461
+ const fingerprint = JSON.stringify({
462
+ settings,
463
+ pluginPaths,
464
+ claudePluginRoots,
465
+ pathContext: options.pathContext?.customized ? options.pathContext : null
466
+ });
467
+ const key = cacheKey(cwd, scope);
468
+ const now = Date.now();
469
+ const cached = discoveryCache.get(key);
470
+ if (!options.force && cached
471
+ && cached.fingerprint === fingerprint
472
+ && now - cached.checkedAt < DISCOVERY_CACHE_TTL_MS) {
473
+ return cloneSkills(cached.skills);
474
+ }
475
+ const discovered = _discoverOmpSkills(service, { ...options, cwd, scope }, settings, pluginPaths, claudePluginRoots);
476
+ discoveryCache.set(key, { fingerprint, checkedAt: now, skills: cloneSkills(discovered) });
477
+ return cloneSkills(discovered);
478
+ }
479
+
353
480
  module.exports = {
354
481
  DEFAULT_SKILL_SETTINGS,
355
482
  discoverOmpSkills,