coding-tool-x 3.8.4 → 3.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
  10. package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-D7CCOFQC.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  29. package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
  34. package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
  36. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  37. package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
  45. package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
  51. package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
  52. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
  59. package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
  62. package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  64. package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-C0EHRym0.css +1 -0
  71. package/dist/web/assets/index-DnEocV0M.js +2 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +8 -4
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +94 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +162 -0
  140. package/src/platforms/channel-auth-service.js +82 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
  269. package/src/{server/services → platforms}/notification-hooks.js +140 -1023
  270. package/src/platforms/oauth-credentials-service.js +506 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +11 -20
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +1 -1
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +780 -213
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -0,0 +1,550 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getPlatformContext } = require('../platform-context');
6
+ const { PATHS } = require('../../config/paths');
7
+ const { ControlManifestStore } = require('./control-manifest-store');
8
+ const { EffectiveControlService, deriveMcpPolicy } = require('./effective-control-service');
9
+ const { validateKnownProjectCwd } = require('./project-path-validation');
10
+ const {
11
+ assertExistingProjectRoot,
12
+ assertNoSymlinkComponents,
13
+ redactSecrets,
14
+ validateMcpId,
15
+ mergeMcpPatch
16
+ } = require('../../shared/project-config');
17
+ const PROJECT_SCOPES = new Set(['user', 'project']);
18
+
19
+ function normalizePlatform(platform) {
20
+ const normalized = String(platform || '').trim().toLowerCase();
21
+ if (!normalized) throw new Error('Platform is required');
22
+ return normalized;
23
+ }
24
+ function createProjectConfigAdapters({ registry, runtime, fsImpl }) {
25
+ const adapters = new Map();
26
+ const manifests = registry?.list?.({ enabledOnly: true }) || [];
27
+
28
+ for (const manifest of manifests) {
29
+ const driver = runtime?.getDriver?.(manifest.key, 'projectConfig');
30
+ if (!driver || typeof driver.createAdapter !== 'function') continue;
31
+ const adapter = driver.createAdapter({ manifest, fsImpl, registry });
32
+ if (adapter) adapters.set(manifest.key, adapter);
33
+ }
34
+
35
+ return adapters;
36
+ }
37
+
38
+ function secretRefsForMcpSpec(spec = {}) {
39
+ const env = spec.env && typeof spec.env === 'object' && !Array.isArray(spec.env) ? spec.env : {};
40
+ const headers = spec.headers && typeof spec.headers === 'object' && !Array.isArray(spec.headers) ? spec.headers : {};
41
+ const refs = [];
42
+ for (const key of Object.keys(env)) refs.push(`env:${key}`);
43
+ for (const key of Object.keys(headers)) refs.push(`header:${key}`);
44
+ if (spec.url) refs.push('url:configured');
45
+ return refs;
46
+ }
47
+
48
+
49
+ function assertProjectSecretReferences(spec = {}) {
50
+ const safeLiteralEnvKeys = new Set(['DEBUG', 'LOG_LEVEL', 'NODE_ENV', 'PYTHONUNBUFFERED']);
51
+ const safeLiteralHeaderKeys = new Set(['accept', 'content-type', 'user-agent']);
52
+ const environmentReference = /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/;
53
+ const sensitiveUrlKey = /^(?:token|secret|password|api[-_]?key|access[-_]?token|authorization|key)$/i;
54
+ const forbiddenFields = new Set([
55
+ 'envvars',
56
+ 'experimentalenvironment',
57
+ 'auth',
58
+ 'oauth',
59
+ 'bearertokenenvvar',
60
+ 'accesstoken',
61
+ 'clientsecret',
62
+ 'credential',
63
+ 'credentials',
64
+ 'secret',
65
+ 'secretrefs',
66
+ 'authorization'
67
+ ]);
68
+ const normalizeKey = key => String(key).replace(/[_-]/g, '').toLowerCase();
69
+ const assertMapReferences = (container, values) => {
70
+ if (!values || typeof values !== 'object' || Array.isArray(values)) {
71
+ throw new Error(`Project MCP ${container} must be an object`);
72
+ }
73
+ for (const [key, value] of Object.entries(values)) {
74
+ const normalizedKey = key.toLowerCase();
75
+ const safeLiteral = container === 'env'
76
+ ? safeLiteralEnvKeys.has(key)
77
+ : safeLiteralHeaderKeys.has(normalizedKey);
78
+ if (safeLiteral && typeof value === 'string' && !value.includes('\n')) continue;
79
+ if (typeof value !== 'string' || !environmentReference.test(value)) {
80
+ throw new Error(`Project MCP ${container}.${key} must reference an environment variable`);
81
+ }
82
+ }
83
+ };
84
+ const commandFlagPattern = /^(?:--?|\/)(?:token|secret|password|api[-_]?key|access[_-]?token|authorization|key|auth|credential|client[-_]?secret|bearer)$/i;
85
+ const commandInlinePattern = /^((?:--?|\/)(?:token|secret|password|api[-_]?key|access[_-]?token|authorization|key|auth|credential|client[-_]?secret|bearer)=)(.*)$/i;
86
+ const assertCommandReferences = (container, value) => {
87
+ const assertValue = (candidate) => {
88
+ if (typeof candidate !== 'string' || !environmentReference.test(candidate)) {
89
+ throw new Error(`Project MCP ${container} secrets must reference an environment variable`);
90
+ }
91
+ };
92
+ if (Array.isArray(value)) {
93
+ let expectsValue = false;
94
+ for (const item of value) {
95
+ if (typeof item !== 'string') {
96
+ throw new Error(`Project MCP ${container} arguments must be strings`);
97
+ }
98
+ if (expectsValue) {
99
+ assertValue(item);
100
+ expectsValue = false;
101
+ continue;
102
+ }
103
+ if (commandFlagPattern.test(item)) {
104
+ expectsValue = true;
105
+ continue;
106
+ }
107
+ const inline = item.match(commandInlinePattern);
108
+ if (inline) {
109
+ assertValue(inline[2]);
110
+ }
111
+ }
112
+ if (expectsValue) throw new Error(`Project MCP ${container} secret flag requires an environment reference`);
113
+ return;
114
+ }
115
+ if (typeof value !== 'string') {
116
+ throw new Error(`Project MCP ${container} must be a string or string array`);
117
+ }
118
+ const matches = [...value.matchAll(/(?:^|\s)(?:--?|\/)(?:token|secret|password|api[-_]?key|access[_-]?token|authorization|key|auth|credential|client[-_]?secret|bearer)(?:=|\s+)(\S+)/gi)];
119
+ matches.forEach(match => assertValue(match[1]));
120
+ };
121
+ const inspect = (value, parentKey = '') => {
122
+ if (Array.isArray(value)) {
123
+ value.forEach(item => inspect(item, parentKey));
124
+ return;
125
+ }
126
+ if (!value || typeof value !== 'object') return;
127
+ for (const [key, child] of Object.entries(value)) {
128
+ const normalizedKey = normalizeKey(key);
129
+ if (normalizedKey === 'env' || normalizedKey === 'environment') {
130
+ assertMapReferences('env', child);
131
+ continue;
132
+ }
133
+ if (normalizedKey === 'headers' || normalizedKey === 'httpheaders') {
134
+ assertMapReferences('headers', child);
135
+ continue;
136
+ }
137
+ if (normalizedKey === 'command' || normalizedKey === 'args') {
138
+ assertCommandReferences(key, child);
139
+ continue;
140
+ }
141
+ if (forbiddenFields.has(normalizedKey) || /(?:token|secret|password|apikey|privatekey)/i.test(normalizedKey)) {
142
+ throw new Error(`Project MCP field ${parentKey ? `${parentKey}.` : ''}${key} must not contain inline secrets`);
143
+ }
144
+ inspect(child, key);
145
+ }
146
+ };
147
+ inspect(spec);
148
+ if (typeof spec.url === 'string') {
149
+ try {
150
+ const parsed = new URL(spec.url);
151
+ if (
152
+ parsed.username
153
+ || parsed.password
154
+ || [...parsed.searchParams.keys()].some(key => sensitiveUrlKey.test(key))
155
+ ) {
156
+ throw new Error('Project MCP URL must not contain inline credentials');
157
+ }
158
+ } catch (error) {
159
+ if (error.message === 'Project MCP URL must not contain inline credentials') throw error;
160
+ if (/\/\/[^/@\s]+(?:\/|$)/.test(spec.url) && /\/\/[^/@\s]+@/.test(spec.url)) {
161
+ throw new Error('Project MCP URL must not contain inline credentials');
162
+ }
163
+ if (/[?&](?:token|secret|password|api[-_]?key|access[-_]?token|authorization|key)=/i.test(spec.url)) {
164
+ throw new Error('Project MCP URL must not contain inline credentials');
165
+ }
166
+ }
167
+ }
168
+ }
169
+ function sanitizeErrorText(value) {
170
+ return String(value || '')
171
+ .replace(/(https?:\/\/[^\s?]+)\?[^\s]+/gi, '$1?[REDACTED]')
172
+ .replace(/(Bearer\s+)[^\s]+/gi, '$1[REDACTED]')
173
+ .replace(/([?&](?:access[_-]?token|api[-_]?key|token|secret|password|authorization|key)=)[^&\s]+/gi, '$1[REDACTED]')
174
+ .replace(/((?:access[_-]?token|api[-_]?key|token|secret|password|authorization|key)\s*[:=]\s*)[^,\s}]+/gi, '$1[REDACTED]');
175
+ }
176
+
177
+ function resolveProjectMcpCwd(projectRoot, cwd, fsImpl = fs) {
178
+ const candidate = cwd
179
+ ? (path.isAbsolute(String(cwd)) ? path.resolve(String(cwd)) : path.resolve(projectRoot, String(cwd)))
180
+ : projectRoot;
181
+ const canonical = assertExistingProjectRoot(candidate, fsImpl);
182
+ const relative = path.relative(projectRoot, canonical);
183
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
184
+ throw new Error('Project MCP cwd must remain inside the project root');
185
+ }
186
+ assertNoSymlinkComponents(projectRoot, canonical, fsImpl);
187
+ return canonical;
188
+ }
189
+
190
+ function unsupportedResource(pathname = null) {
191
+ return {
192
+ supported: false,
193
+ path: pathname,
194
+ exists: false,
195
+ content: '',
196
+ updatedAt: null
197
+ };
198
+ }
199
+
200
+ class ProjectConfigService {
201
+ constructor({
202
+ registry = getPlatformContext().registry,
203
+ runtime = getPlatformContext().runtime,
204
+ adapters = null,
205
+ validateProjectPath = validateKnownProjectCwd,
206
+ skillServiceFactory = null,
207
+ controlService = null,
208
+ mcpClientFactory = null,
209
+ fsImpl = fs
210
+ } = {}) {
211
+ this.registry = registry;
212
+ this.runtime = runtime;
213
+ this.fs = fsImpl;
214
+ this.validateProjectPath = validateProjectPath;
215
+ this.adapters = adapters || createProjectConfigAdapters({ registry, runtime, fsImpl });
216
+ this.skillServiceFactory = skillServiceFactory;
217
+ this.controlService = controlService || (
218
+ PATHS.effectiveControlManifest
219
+ ? new EffectiveControlService({
220
+ store: new ControlManifestStore({
221
+ userPath: PATHS.effectiveControlManifest,
222
+ projectPathResolver: ({ projectPath }) => path.join(projectPath, '.ctx-control.json'),
223
+ fsImpl
224
+ })
225
+ })
226
+ : null
227
+ );
228
+ this.mcpClientFactory = mcpClientFactory;
229
+ }
230
+
231
+ getAdapter(platform) {
232
+ const key = normalizePlatform(platform);
233
+ if (!this.registry?.resolve?.(key)) {
234
+ throw new Error(`Unsupported platform: ${key}`);
235
+ }
236
+
237
+ const adapter = this.adapters instanceof Map ? this.adapters.get(key) : this.adapters?.[key];
238
+ if (!adapter) throw new Error(`Project configuration is not supported for ${key}`);
239
+ return { key, adapter };
240
+ }
241
+
242
+ async _canonicalProjectPath(projectPath) {
243
+ try {
244
+ if (typeof projectPath !== 'string' || !projectPath.trim()) {
245
+ throw new Error('projectPath is required');
246
+ }
247
+ const validated = await this.validateProjectPath(projectPath);
248
+ if (!validated) throw new Error('projectPath is required');
249
+ return assertExistingProjectRoot(validated, this.fs);
250
+ } catch (error) {
251
+ if (error?.message?.startsWith('Invalid project path')) throw error;
252
+ const wrapped = new Error(`Invalid project path: ${error?.message || String(error)}`);
253
+ wrapped.cause = error;
254
+ throw wrapped;
255
+ }
256
+ }
257
+
258
+ async _resolve(projectPath, platform) {
259
+ const { key, adapter } = this.getAdapter(platform);
260
+ const projectRoot = await this._canonicalProjectPath(projectPath);
261
+ return { key, adapter, projectRoot };
262
+ }
263
+
264
+ async readInstruction(projectPath, platform) {
265
+ const { adapter, projectRoot } = await this._resolve(projectPath, platform);
266
+ return adapter.readInstruction(projectRoot);
267
+ }
268
+
269
+ async writeInstruction(projectPath, platform, content) {
270
+ if (typeof content !== 'string') throw new Error('Instruction content must be a string');
271
+ const { adapter, projectRoot } = await this._resolve(projectPath, platform);
272
+ return adapter.writeInstruction(projectRoot, content);
273
+ }
274
+
275
+ async deleteInstruction(projectPath, platform) {
276
+ const { adapter, projectRoot } = await this._resolve(projectPath, platform);
277
+ return adapter.deleteInstruction(projectRoot);
278
+ }
279
+
280
+
281
+
282
+
283
+ async listProjectSkills(projectPath, platform) {
284
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
285
+ const description = adapter.describe().skills;
286
+ if (!description.supported) {
287
+ return { supported: false, project: [], inherited: [], path: null };
288
+ }
289
+
290
+ const service = this.skillServiceFactory
291
+ ? this.skillServiceFactory(key, { registry: this.registry })
292
+ : new (require('./skill-service').SkillService)(key, { registry: this.registry });
293
+ const scanResult = typeof service.scanSkills === 'function'
294
+ ? await service.scanSkills({ scope: 'project', cwd: projectRoot })
295
+ : { skills: await service.listSkills(false, { scope: 'project', cwd: projectRoot }) };
296
+ const skills = scanResult.skills || [];
297
+ const project = skills.filter(skill => skill.scope === 'project' || skill.sourceScope === 'project');
298
+ const inherited = skills.filter(skill => skill.scope === 'user' || skill.sourceScope === 'user');
299
+ return {
300
+ supported: true,
301
+ project,
302
+ inherited,
303
+ path: description.canonicalRoot,
304
+ platform: key,
305
+ ...(scanResult.refresh ? { refresh: scanResult.refresh } : {})
306
+ };
307
+ }
308
+
309
+ async setProjectSkillEnabled(projectPath, platform, controlKey, enabled) {
310
+ if (!controlKey || !String(controlKey).trim()) {
311
+ throw new Error('Skill controlKey is required');
312
+ }
313
+ const { key, projectRoot } = await this._resolve(projectPath, platform);
314
+ const service = this.skillServiceFactory
315
+ ? this.skillServiceFactory(key, { registry: this.registry })
316
+ : new (require('./skill-service').SkillService)(key, { registry: this.registry });
317
+ const controlService = service.controlService || this.controlService;
318
+ if (!controlService || typeof controlService.setSkillEnabled !== 'function') {
319
+ throw new Error('Effective control service is unavailable');
320
+ }
321
+ return controlService.setSkillEnabled({
322
+ platform: key,
323
+ controlKey: String(controlKey).trim(),
324
+ scope: 'project',
325
+ projectPath: projectRoot,
326
+ enabled: enabled === true
327
+ });
328
+ }
329
+
330
+
331
+ async listProjectMcp(projectPath, platform) {
332
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
333
+ const description = adapter.describe().mcp;
334
+ const result = adapter.readProjectMcp(projectRoot);
335
+ const servers = (result.servers || []).map(server => {
336
+ const controlKey = `mcp:${key}:project:${server.id}`;
337
+ let controlled = this.controlService?.getMcp?.(controlKey, {
338
+ scope: 'project',
339
+ projectPath: projectRoot
340
+ }) || null;
341
+ if (!controlled && this.controlService?.registerMcp) {
342
+ controlled = this.controlService.registerMcp({
343
+ kind: 'mcp',
344
+ controlKey,
345
+ platform: key,
346
+ scope: 'project',
347
+ projectPath: projectRoot,
348
+ name: server.id,
349
+ source: 'project-native',
350
+ enabled: server.enabled !== false,
351
+ trust: 'approved',
352
+ ...deriveMcpPolicy({ transport: server.server?.type, scope: 'project' }),
353
+ secretRefs: secretRefsForMcpSpec(server.server || {}),
354
+ managed: false
355
+ });
356
+ }
357
+ return {
358
+ ...redactSecrets({ ...server, scope: 'project' }),
359
+ controlKey,
360
+ managed: controlled?.managed === true,
361
+ external: controlled ? controlled.managed !== true : true,
362
+ riskTier: controlled?.riskTier || deriveMcpPolicy({ transport: server.server?.type, scope: 'project' }).riskTier,
363
+ egressProfile: controlled?.egressProfile || deriveMcpPolicy({ transport: server.server?.type, scope: 'project' }).egressProfile,
364
+ trust: controlled?.trust || 'approved',
365
+ enabled: controlled?.managed === true
366
+ ? controlled.enabled === true
367
+ : server.enabled !== false
368
+ };
369
+ });
370
+ return {
371
+ supported: result.supported !== undefined ? result.supported : description.supported,
372
+ path: result.path ?? description.path,
373
+ format: result.format || description.format,
374
+ servers
375
+ };
376
+ }
377
+
378
+ async upsertProjectMcp(projectPath, platform, id, spec) {
379
+ const normalizedId = validateMcpId(id);
380
+ if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
381
+ throw new Error('MCP server spec must be an object');
382
+ }
383
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
384
+ const existingSpec = adapter.readProjectMcpSpec(projectRoot, normalizedId) || {};
385
+ const controlKey = `mcp:${key}:project:${normalizedId}`;
386
+ const existingControl = this.controlService?.getMcp?.(controlKey, {
387
+ scope: 'project',
388
+ projectPath: projectRoot
389
+ }) || null;
390
+ if ((existingControl && existingControl.managed !== true) || (!existingControl && Object.keys(existingSpec).length > 0)) {
391
+ return {
392
+ success: false,
393
+ status: 'unsupported',
394
+ external: true,
395
+ managed: false,
396
+ id: normalizedId,
397
+ scope: 'project'
398
+ };
399
+ }
400
+ const { validateServerSpec } = require('./mcp-service');
401
+ const mergedSpec = mergeMcpPatch(existingSpec, spec);
402
+ assertProjectSecretReferences(mergedSpec);
403
+ validateServerSpec(mergedSpec);
404
+ const result = adapter.upsertProjectMcp(projectRoot, normalizedId, spec);
405
+ if (this.controlService?.registerMcp) {
406
+ const persistedSpec = adapter.readProjectMcpSpec(projectRoot, normalizedId) || {};
407
+ const managed = existingControl
408
+ ? existingControl.managed === true
409
+ : Object.keys(existingSpec).length === 0;
410
+ this.controlService.registerMcp({
411
+ ...(existingControl || {}),
412
+ kind: 'mcp',
413
+ controlKey,
414
+ platform: key,
415
+ scope: 'project',
416
+ projectPath: projectRoot,
417
+ name: normalizedId,
418
+ source: existingControl?.source || 'project-native',
419
+ enabled: existingControl ? existingControl.enabled === true : result.enabled !== false,
420
+ trust: existingControl?.trust || 'approved',
421
+ ...deriveMcpPolicy({ transport: persistedSpec?.type, scope: 'project' }),
422
+ secretRefs: secretRefsForMcpSpec(persistedSpec),
423
+ managed
424
+ });
425
+ }
426
+ return redactSecrets({ ...result, id: normalizedId, scope: 'project' });
427
+ }
428
+
429
+ async removeProjectMcp(projectPath, platform, id) {
430
+ const normalizedId = validateMcpId(id);
431
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
432
+ const controlKey = `mcp:${key}:project:${normalizedId}`;
433
+ const controlled = this.controlService?.getMcp?.(controlKey, {
434
+ scope: 'project',
435
+ projectPath: projectRoot
436
+ }) || null;
437
+ if (!controlled || controlled.managed !== true) {
438
+ return {
439
+ success: false,
440
+ status: 'unsupported',
441
+ external: true,
442
+ managed: false,
443
+ id: normalizedId,
444
+ scope: 'project'
445
+ };
446
+ }
447
+ const result = adapter.removeProjectMcp(projectRoot, normalizedId);
448
+ this.controlService?.removeMcp?.({
449
+ controlKey,
450
+ scope: 'project',
451
+ projectPath: projectRoot
452
+ });
453
+ return redactSecrets({ ...result, id: normalizedId, scope: 'project' });
454
+ }
455
+ async testProjectMcp(projectPath, platform, id) {
456
+ const normalizedId = validateMcpId(id);
457
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
458
+ const description = adapter.describe().mcp;
459
+ if (!description.supported) {
460
+ return { success: false, status: 'unsupported', platform: key, id: normalizedId };
461
+ }
462
+ const spec = adapter.readProjectMcpSpec(projectRoot, normalizedId);
463
+ if (!spec) throw new Error(`MCP server "${normalizedId}" is not configured for this project`);
464
+ const effectiveSpec = { ...spec };
465
+ if ((effectiveSpec.type || 'stdio') === 'stdio') {
466
+ effectiveSpec.cwd = resolveProjectMcpCwd(projectRoot, effectiveSpec.cwd, this.fs);
467
+ }
468
+ const controlKey = `mcp:${key}:project:${normalizedId}`;
469
+ const control = this.controlService?.getMcp?.(controlKey, {
470
+ scope: 'project',
471
+ projectPath: projectRoot
472
+ });
473
+ if (!control || control.managed !== true || control.trust !== 'approved' || control.enabled !== true) {
474
+ const error = new Error('MCP server is disabled by the effective control policy');
475
+ error.code = 'MCP_DISABLED';
476
+ throw error;
477
+ }
478
+ const client = this.mcpClientFactory
479
+ ? this.mcpClientFactory(effectiveSpec, { projectPath: projectRoot, platform: key })
480
+ : new (require('./mcp-client').McpClient)(effectiveSpec);
481
+ const startedAt = Date.now();
482
+
483
+ try {
484
+ await client.connect();
485
+ if (typeof client.initialize === 'function') {
486
+ await client.initialize();
487
+ }
488
+ const tools = typeof client.listTools === 'function' ? await client.listTools() : [];
489
+ return {
490
+ success: true,
491
+ platform: key,
492
+ id: normalizedId,
493
+ scope: 'project',
494
+ tools: Array.isArray(tools) ? tools.map(tool => ({ name: tool.name })) : [],
495
+ duration: Date.now() - startedAt
496
+ };
497
+ } catch (error) {
498
+ return {
499
+ success: false,
500
+ platform: key,
501
+ id: normalizedId,
502
+ scope: 'project',
503
+ message: sanitizeErrorText(error.message),
504
+ duration: Date.now() - startedAt
505
+ };
506
+ } finally {
507
+ try {
508
+ if (typeof client.disconnect === 'function') {
509
+ await client.disconnect();
510
+ } else {
511
+ await client.close?.();
512
+ }
513
+ } catch (_) {}
514
+ }
515
+ }
516
+
517
+ async getSnapshot(projectPath, platform) {
518
+ const { key, adapter, projectRoot } = await this._resolve(projectPath, platform);
519
+ const description = adapter.describe();
520
+ const instruction = adapter.readInstruction(projectRoot);
521
+ const mcp = await this.listProjectMcp(projectRoot, key);
522
+ const skills = description.skills?.supported !== false
523
+ ? await this.listProjectSkills(projectRoot, key)
524
+ : { supported: false, project: [], inherited: [], path: null };
525
+
526
+ return {
527
+ success: true,
528
+ projectPath: projectRoot,
529
+ platform: key,
530
+ instruction,
531
+ skills,
532
+ mcp: {
533
+ supported: mcp.supported !== undefined ? mcp.supported : description.mcp?.supported === true,
534
+ path: mcp.path ?? description.mcp?.path ?? null,
535
+ format: mcp.format || description.mcp?.format || 'none',
536
+ servers: (mcp.servers || []).map(server => redactSecrets({ ...server, scope: 'project' }))
537
+ },
538
+ capabilities: {
539
+ instruction: description.instruction?.supported === true,
540
+ skills: description.skills?.supported === true,
541
+ mcp: description.mcp?.supported === true
542
+ }
543
+ };
544
+ }
545
+ }
546
+
547
+ module.exports = {
548
+ ProjectConfigService,
549
+ PROJECT_SCOPES
550
+ };
@@ -42,6 +42,35 @@ async function getKnownProjectPaths() {
42
42
  return known;
43
43
  }
44
44
 
45
+ async function resolveKnownProjectPath(rawPath) {
46
+ if (rawPath == null || String(rawPath).trim() === '') return null;
47
+ const input = String(rawPath).trim();
48
+ if (!path.isAbsolute(input)) return null;
49
+
50
+ const resolvedInput = path.resolve(input);
51
+ let resolved;
52
+ try {
53
+ resolved = fs.realpathSync(resolvedInput);
54
+ } catch (error) {
55
+ if (error.code !== 'ENOENT') return null;
56
+ try {
57
+ resolved = path.join(fs.realpathSync(path.dirname(resolvedInput)), path.basename(resolvedInput));
58
+ } catch (_) {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ const known = await getKnownProjectPaths();
64
+ return Array.from(known).some(root => {
65
+ const relative = path.relative(root, resolved);
66
+ return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
67
+ }) ? resolved : null;
68
+ }
69
+
70
+ async function isKnownProjectPath(rawPath) {
71
+ return Boolean(await resolveKnownProjectPath(rawPath));
72
+ }
73
+
45
74
  async function validateKnownProjectCwd(rawCwd) {
46
75
  if (rawCwd == null || String(rawCwd).trim() === '') return null;
47
76
  const input = String(rawCwd).trim();
@@ -61,5 +90,7 @@ async function validateKnownProjectCwd(rawCwd) {
61
90
 
62
91
  module.exports = {
63
92
  getKnownProjectPaths,
93
+ resolveKnownProjectPath,
94
+ isKnownProjectPath,
64
95
  validateKnownProjectCwd
65
96
  };