@kaitranntt/ccs 7.63.1 → 7.64.0-dev.2

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 (254) hide show
  1. package/README.md +12 -3
  2. package/dist/api/services/profile-lifecycle-service.js +4 -4
  3. package/dist/api/services/profile-lifecycle-service.js.map +1 -1
  4. package/dist/api/services/profile-types.d.ts +17 -0
  5. package/dist/api/services/profile-types.d.ts.map +1 -1
  6. package/dist/api/services/profile-writer.d.ts.map +1 -1
  7. package/dist/api/services/profile-writer.js +3 -5
  8. package/dist/api/services/profile-writer.js.map +1 -1
  9. package/dist/ccs.js +82 -15
  10. package/dist/ccs.js.map +1 -1
  11. package/dist/cliproxy/accounts/email-account-identity.d.ts +12 -0
  12. package/dist/cliproxy/accounts/email-account-identity.d.ts.map +1 -0
  13. package/dist/cliproxy/accounts/email-account-identity.js +124 -0
  14. package/dist/cliproxy/accounts/email-account-identity.js.map +1 -0
  15. package/dist/cliproxy/accounts/query.d.ts.map +1 -1
  16. package/dist/cliproxy/accounts/query.js +15 -8
  17. package/dist/cliproxy/accounts/query.js.map +1 -1
  18. package/dist/cliproxy/accounts/registry.d.ts +6 -0
  19. package/dist/cliproxy/accounts/registry.d.ts.map +1 -1
  20. package/dist/cliproxy/accounts/registry.js +136 -42
  21. package/dist/cliproxy/accounts/registry.js.map +1 -1
  22. package/dist/cliproxy/auth/token-manager.d.ts.map +1 -1
  23. package/dist/cliproxy/auth/token-manager.js +45 -11
  24. package/dist/cliproxy/auth/token-manager.js.map +1 -1
  25. package/dist/cliproxy/executor/env-resolver.d.ts +27 -0
  26. package/dist/cliproxy/executor/env-resolver.d.ts.map +1 -1
  27. package/dist/cliproxy/executor/env-resolver.js +87 -3
  28. package/dist/cliproxy/executor/env-resolver.js.map +1 -1
  29. package/dist/cliproxy/executor/index.d.ts.map +1 -1
  30. package/dist/cliproxy/executor/index.js +55 -12
  31. package/dist/cliproxy/executor/index.js.map +1 -1
  32. package/dist/cliproxy/model-catalog.d.ts +6 -0
  33. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  34. package/dist/cliproxy/model-catalog.js +38 -1
  35. package/dist/cliproxy/model-catalog.js.map +1 -1
  36. package/dist/cliproxy/proxy-config-resolver.d.ts +2 -1
  37. package/dist/cliproxy/proxy-config-resolver.d.ts.map +1 -1
  38. package/dist/cliproxy/proxy-config-resolver.js +1 -0
  39. package/dist/cliproxy/proxy-config-resolver.js.map +1 -1
  40. package/dist/cliproxy/proxy-target-resolver.d.ts +2 -0
  41. package/dist/cliproxy/proxy-target-resolver.d.ts.map +1 -1
  42. package/dist/cliproxy/proxy-target-resolver.js +3 -0
  43. package/dist/cliproxy/proxy-target-resolver.js.map +1 -1
  44. package/dist/cliproxy/quota-fetcher-codex.d.ts +0 -3
  45. package/dist/cliproxy/quota-fetcher-codex.d.ts.map +1 -1
  46. package/dist/cliproxy/quota-fetcher-codex.js +46 -17
  47. package/dist/cliproxy/quota-fetcher-codex.js.map +1 -1
  48. package/dist/cliproxy/remote-auth-fetcher.d.ts.map +1 -1
  49. package/dist/cliproxy/remote-auth-fetcher.js +89 -8
  50. package/dist/cliproxy/remote-auth-fetcher.js.map +1 -1
  51. package/dist/cliproxy/services/variant-settings.d.ts.map +1 -1
  52. package/dist/cliproxy/services/variant-settings.js +23 -10
  53. package/dist/cliproxy/services/variant-settings.js.map +1 -1
  54. package/dist/cliproxy/stats-transformer.d.ts.map +1 -1
  55. package/dist/cliproxy/stats-transformer.js +26 -3
  56. package/dist/cliproxy/stats-transformer.js.map +1 -1
  57. package/dist/cliproxy/types.d.ts +2 -0
  58. package/dist/cliproxy/types.d.ts.map +1 -1
  59. package/dist/commands/cliproxy/quota-subcommand.d.ts.map +1 -1
  60. package/dist/commands/cliproxy/quota-subcommand.js +25 -22
  61. package/dist/commands/cliproxy/quota-subcommand.js.map +1 -1
  62. package/dist/commands/cliproxy/variant-subcommand.d.ts.map +1 -1
  63. package/dist/commands/cliproxy/variant-subcommand.js +14 -6
  64. package/dist/commands/cliproxy/variant-subcommand.js.map +1 -1
  65. package/dist/commands/config-image-analysis-command.d.ts.map +1 -1
  66. package/dist/commands/config-image-analysis-command.js +87 -1
  67. package/dist/commands/config-image-analysis-command.js.map +1 -1
  68. package/dist/commands/install-command.d.ts.map +1 -1
  69. package/dist/commands/install-command.js +8 -2
  70. package/dist/commands/install-command.js.map +1 -1
  71. package/dist/config/unified-config-loader.d.ts.map +1 -1
  72. package/dist/config/unified-config-loader.js +9 -4
  73. package/dist/config/unified-config-loader.js.map +1 -1
  74. package/dist/config/unified-config-types.d.ts +4 -0
  75. package/dist/config/unified-config-types.d.ts.map +1 -1
  76. package/dist/config/unified-config-types.js +4 -2
  77. package/dist/config/unified-config-types.js.map +1 -1
  78. package/dist/copilot/copilot-executor.d.ts +13 -0
  79. package/dist/copilot/copilot-executor.d.ts.map +1 -1
  80. package/dist/copilot/copilot-executor.js +63 -4
  81. package/dist/copilot/copilot-executor.js.map +1 -1
  82. package/dist/delegation/executor/result-aggregator.d.ts +2 -1
  83. package/dist/delegation/executor/result-aggregator.d.ts.map +1 -1
  84. package/dist/delegation/executor/result-aggregator.js +21 -1
  85. package/dist/delegation/executor/result-aggregator.js.map +1 -1
  86. package/dist/delegation/executor/types.d.ts +6 -0
  87. package/dist/delegation/executor/types.d.ts.map +1 -1
  88. package/dist/delegation/headless-executor.d.ts.map +1 -1
  89. package/dist/delegation/headless-executor.js +69 -4
  90. package/dist/delegation/headless-executor.js.map +1 -1
  91. package/dist/management/checks/image-analysis-check.js +1 -1
  92. package/dist/management/checks/image-analysis-check.js.map +1 -1
  93. package/dist/management/instance-manager.d.ts +1 -1
  94. package/dist/management/instance-manager.d.ts.map +1 -1
  95. package/dist/management/instance-manager.js +10 -2
  96. package/dist/management/instance-manager.js.map +1 -1
  97. package/dist/shared/compatible-cli-contracts.d.ts +4 -0
  98. package/dist/shared/compatible-cli-contracts.d.ts.map +1 -1
  99. package/dist/targets/codex-adapter.d.ts.map +1 -1
  100. package/dist/targets/codex-adapter.js +78 -3
  101. package/dist/targets/codex-adapter.js.map +1 -1
  102. package/dist/targets/codex-detector.d.ts.map +1 -1
  103. package/dist/targets/codex-detector.js +28 -7
  104. package/dist/targets/codex-detector.js.map +1 -1
  105. package/dist/types/config.d.ts +5 -0
  106. package/dist/types/config.d.ts.map +1 -1
  107. package/dist/types/config.js.map +1 -1
  108. package/dist/ui/assets/{accounts-DkxZnPJE.js → accounts-BHEYnq6b.js} +1 -1
  109. package/dist/ui/assets/{alert-dialog-CiYMglgR.js → alert-dialog-D0EFRcfB.js} +1 -1
  110. package/dist/ui/assets/api-DhM3BYXr.js +4 -0
  111. package/dist/ui/assets/{auth-section-BMaKBRA_.js → auth-section-DVp8FQGm.js} +1 -1
  112. package/dist/ui/assets/{backups-section-DOpSADoH.js → backups-section-CRo0NZkA.js} +1 -1
  113. package/dist/ui/assets/channels-uZ_9CBqO.js +1 -0
  114. package/dist/ui/assets/checkbox-32DNqW_Q.js +1 -0
  115. package/dist/ui/assets/{claude-extension-B5RngGem.js → claude-extension-BfXlz5gV.js} +1 -1
  116. package/dist/ui/assets/cliproxy-DjNY9H-U.js +3 -0
  117. package/dist/ui/assets/{cliproxy-ai-providers-DVaaS-CT.js → cliproxy-ai-providers-5SHLMHiy.js} +5 -5
  118. package/dist/ui/assets/cliproxy-control-panel-Zax_m1AC.js +1 -0
  119. package/dist/ui/assets/codex-CRUSpjsu.js +27 -0
  120. package/dist/ui/assets/{confirm-dialog-B9vRgowr.js → confirm-dialog-DVf5ZmCZ.js} +1 -1
  121. package/dist/ui/assets/copilot-BZrihl_Z.js +3 -0
  122. package/dist/ui/assets/cursor-BP4nbEk_.js +1 -0
  123. package/dist/ui/assets/{droid-DshEfT1H.js → droid-BG92rdM2.js} +2 -2
  124. package/dist/ui/assets/globalenv-section-Cf6dKgSf.js +1 -0
  125. package/dist/ui/assets/{health-CE0VQs6K.js → health-BTy1UZs3.js} +1 -1
  126. package/dist/ui/assets/icons-CeH5899d.js +1 -0
  127. package/dist/ui/assets/index-B6SrL1O-.css +1 -0
  128. package/dist/ui/assets/index-BVeN0dIB.js +1 -0
  129. package/dist/ui/assets/index-Corv1lSo.js +69 -0
  130. package/dist/ui/assets/index-DHrTq-0n.js +1 -0
  131. package/dist/ui/assets/index-DuRYaONg.js +1 -0
  132. package/dist/ui/assets/index-N2ZSJurX.js +1 -0
  133. package/dist/ui/assets/index-wg7UtkFv.js +1 -0
  134. package/dist/ui/assets/{masked-input-B2tcbvAj.js → masked-input-DX9bedLy.js} +1 -1
  135. package/dist/ui/assets/{proxy-status-widget-BnJD49TF.js → proxy-status-widget-DVDMuZK5.js} +1 -1
  136. package/dist/ui/assets/{radix-ui-Dt3edmE5.js → radix-ui-C98W0NRG.js} +1 -1
  137. package/dist/ui/assets/{raw-json-settings-editor-panel-DnUbq1__.js → raw-json-settings-editor-panel-Dkt5E6Z_.js} +1 -1
  138. package/dist/ui/assets/{searchable-select-ULayr5K1.js → searchable-select-BP3Q1-Yn.js} +1 -1
  139. package/dist/ui/assets/separator-BLGGUlh9.js +1 -0
  140. package/dist/ui/assets/shared-G0XRyLig.js +8 -0
  141. package/dist/ui/assets/{table-E5IxHhrW.js → table-B4lRrWC-.js} +1 -1
  142. package/dist/ui/assets/tanstack-CfKik0yL.js +4 -0
  143. package/dist/ui/assets/updates--A2Sdo7N.js +1 -0
  144. package/dist/ui/index.html +5 -5
  145. package/dist/utils/claude-config-path.d.ts +2 -0
  146. package/dist/utils/claude-config-path.d.ts.map +1 -1
  147. package/dist/utils/claude-config-path.js +6 -1
  148. package/dist/utils/claude-config-path.js.map +1 -1
  149. package/dist/utils/hooks/get-image-analysis-hook-env.d.ts +3 -2
  150. package/dist/utils/hooks/get-image-analysis-hook-env.d.ts.map +1 -1
  151. package/dist/utils/hooks/get-image-analysis-hook-env.js +15 -6
  152. package/dist/utils/hooks/get-image-analysis-hook-env.js.map +1 -1
  153. package/dist/utils/hooks/image-analysis-backend-resolver.d.ts +53 -0
  154. package/dist/utils/hooks/image-analysis-backend-resolver.d.ts.map +1 -0
  155. package/dist/utils/hooks/image-analysis-backend-resolver.js +376 -0
  156. package/dist/utils/hooks/image-analysis-backend-resolver.js.map +1 -0
  157. package/dist/utils/hooks/image-analysis-runtime-status.d.ts +17 -0
  158. package/dist/utils/hooks/image-analysis-runtime-status.d.ts.map +1 -0
  159. package/dist/utils/hooks/image-analysis-runtime-status.js +132 -0
  160. package/dist/utils/hooks/image-analysis-runtime-status.js.map +1 -0
  161. package/dist/utils/hooks/image-analyzer-profile-hook-injector.d.ts +6 -5
  162. package/dist/utils/hooks/image-analyzer-profile-hook-injector.d.ts.map +1 -1
  163. package/dist/utils/hooks/image-analyzer-profile-hook-injector.js +37 -17
  164. package/dist/utils/hooks/image-analyzer-profile-hook-injector.js.map +1 -1
  165. package/dist/utils/hooks/index.d.ts +2 -0
  166. package/dist/utils/hooks/index.d.ts.map +1 -1
  167. package/dist/utils/hooks/index.js +8 -1
  168. package/dist/utils/hooks/index.js.map +1 -1
  169. package/dist/utils/websearch/claude-tool-args.d.ts +5 -0
  170. package/dist/utils/websearch/claude-tool-args.d.ts.map +1 -0
  171. package/dist/utils/websearch/claude-tool-args.js +125 -0
  172. package/dist/utils/websearch/claude-tool-args.js.map +1 -0
  173. package/dist/utils/websearch/hook-env.d.ts.map +1 -1
  174. package/dist/utils/websearch/hook-env.js +8 -0
  175. package/dist/utils/websearch/hook-env.js.map +1 -1
  176. package/dist/utils/websearch/hook-installer.d.ts +3 -2
  177. package/dist/utils/websearch/hook-installer.d.ts.map +1 -1
  178. package/dist/utils/websearch/hook-installer.js +3 -2
  179. package/dist/utils/websearch/hook-installer.js.map +1 -1
  180. package/dist/utils/websearch/index.d.ts +3 -0
  181. package/dist/utils/websearch/index.d.ts.map +1 -1
  182. package/dist/utils/websearch/index.js +23 -2
  183. package/dist/utils/websearch/index.js.map +1 -1
  184. package/dist/utils/websearch/mcp-installer.d.ts +14 -0
  185. package/dist/utils/websearch/mcp-installer.d.ts.map +1 -0
  186. package/dist/utils/websearch/mcp-installer.js +351 -0
  187. package/dist/utils/websearch/mcp-installer.js.map +1 -0
  188. package/dist/utils/websearch/profile-hook-injector.d.ts +5 -3
  189. package/dist/utils/websearch/profile-hook-injector.d.ts.map +1 -1
  190. package/dist/utils/websearch/profile-hook-injector.js +5 -3
  191. package/dist/utils/websearch/profile-hook-injector.js.map +1 -1
  192. package/dist/utils/websearch/status.d.ts.map +1 -1
  193. package/dist/utils/websearch/status.js +67 -1
  194. package/dist/utils/websearch/status.js.map +1 -1
  195. package/dist/utils/websearch/trace.d.ts +23 -0
  196. package/dist/utils/websearch/trace.d.ts.map +1 -0
  197. package/dist/utils/websearch/trace.js +206 -0
  198. package/dist/utils/websearch/trace.js.map +1 -0
  199. package/dist/utils/websearch-manager.d.ts +11 -11
  200. package/dist/utils/websearch-manager.d.ts.map +1 -1
  201. package/dist/utils/websearch-manager.js +32 -17
  202. package/dist/utils/websearch-manager.js.map +1 -1
  203. package/dist/web-server/index.d.ts.map +1 -1
  204. package/dist/web-server/index.js +9 -1
  205. package/dist/web-server/index.js.map +1 -1
  206. package/dist/web-server/routes/account-routes.d.ts.map +1 -1
  207. package/dist/web-server/routes/account-routes.js +2 -1
  208. package/dist/web-server/routes/account-routes.js.map +1 -1
  209. package/dist/web-server/routes/cliproxy-auth-routes.d.ts.map +1 -1
  210. package/dist/web-server/routes/cliproxy-auth-routes.js +1 -1
  211. package/dist/web-server/routes/cliproxy-auth-routes.js.map +1 -1
  212. package/dist/web-server/routes/cliproxy-local-proxy.d.ts +20 -0
  213. package/dist/web-server/routes/cliproxy-local-proxy.d.ts.map +1 -0
  214. package/dist/web-server/routes/cliproxy-local-proxy.js +117 -0
  215. package/dist/web-server/routes/cliproxy-local-proxy.js.map +1 -0
  216. package/dist/web-server/routes/image-analysis-routes.d.ts +3 -0
  217. package/dist/web-server/routes/image-analysis-routes.d.ts.map +1 -0
  218. package/dist/web-server/routes/image-analysis-routes.js +362 -0
  219. package/dist/web-server/routes/image-analysis-routes.js.map +1 -0
  220. package/dist/web-server/routes/index.d.ts.map +1 -1
  221. package/dist/web-server/routes/index.js +2 -0
  222. package/dist/web-server/routes/index.js.map +1 -1
  223. package/dist/web-server/routes/settings-routes.d.ts.map +1 -1
  224. package/dist/web-server/routes/settings-routes.js +67 -5
  225. package/dist/web-server/routes/settings-routes.js.map +1 -1
  226. package/dist/web-server/services/codex-dashboard-service.d.ts.map +1 -1
  227. package/dist/web-server/services/codex-dashboard-service.js +27 -8
  228. package/dist/web-server/services/codex-dashboard-service.js.map +1 -1
  229. package/lib/hooks/websearch-transformer.cjs +660 -96
  230. package/lib/mcp/ccs-websearch-server.cjs +339 -0
  231. package/package.json +3 -2
  232. package/scripts/github/normalize-ai-review-output.mjs +232 -16
  233. package/scripts/github/prepare-ai-review-scope.mjs +317 -0
  234. package/dist/ui/assets/api-DaOtMRT4.js +0 -4
  235. package/dist/ui/assets/channels-zDFV-BlC.js +0 -1
  236. package/dist/ui/assets/checkbox-Cb5AZBZL.js +0 -1
  237. package/dist/ui/assets/cliproxy-VYe0Qov1.js +0 -3
  238. package/dist/ui/assets/cliproxy-control-panel-FVIQcFti.js +0 -1
  239. package/dist/ui/assets/codex-D2yIwOs4.js +0 -27
  240. package/dist/ui/assets/copilot-HvsOp6hu.js +0 -3
  241. package/dist/ui/assets/cursor-C1XOjAWS.js +0 -1
  242. package/dist/ui/assets/globalenv-section-CmcMkb6z.js +0 -1
  243. package/dist/ui/assets/icons-EMBHZkGo.js +0 -1
  244. package/dist/ui/assets/index-6dNBcNC3.js +0 -1
  245. package/dist/ui/assets/index-BAuT6yuc.css +0 -1
  246. package/dist/ui/assets/index-CesVGA6m.js +0 -1
  247. package/dist/ui/assets/index-CmKclBR1.js +0 -1
  248. package/dist/ui/assets/index-CmtSgCxo.js +0 -1
  249. package/dist/ui/assets/index-DAtuJuGe.js +0 -69
  250. package/dist/ui/assets/separator--ZH5ZM-3.js +0 -1
  251. package/dist/ui/assets/shared-qizFb9Ye.js +0 -8
  252. package/dist/ui/assets/switch-DmDIWykO.js +0 -1
  253. package/dist/ui/assets/tanstack-B8i0evp-.js +0 -4
  254. package/dist/ui/assets/updates-2Uu4Mgtg.js +0 -1
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {
4
+ getActiveProviderIds,
5
+ getQueryFingerprint,
6
+ getSkipReason,
7
+ hasAnyActiveProviders,
8
+ runLocalWebSearch,
9
+ shouldSkipHook,
10
+ traceWebSearchEvent,
11
+ } = require('../hooks/websearch-transformer.cjs');
12
+
13
+ const PROTOCOL_VERSION = '2024-11-05';
14
+ const SERVER_NAME = 'ccs-websearch';
15
+ const SERVER_VERSION = '1.0.0';
16
+ const TOOL_NAME = 'WebSearch';
17
+ const TOOL_ALIASES = ['search'];
18
+ const TOOL_DESCRIPTION =
19
+ 'Third-party WebSearch replacement for CCS-managed Claude launches. Use this instead of Bash/curl/http fetches for web lookups. Provider order: Exa, Tavily, Brave Search, DuckDuckGo, then optional legacy CLI fallback.';
20
+
21
+ function isSupportedToolName(name) {
22
+ return name === TOOL_NAME || TOOL_ALIASES.includes(name);
23
+ }
24
+
25
+ let inputBuffer = Buffer.alloc(0);
26
+ const sessionState = {
27
+ initializeCount: 0,
28
+ toolsListCount: 0,
29
+ exposed: false,
30
+ toolCalls: 0,
31
+ };
32
+ let sessionSummaryWritten = false;
33
+
34
+ function shouldExposeTools() {
35
+ return !shouldSkipHook() && hasAnyActiveProviders();
36
+ }
37
+
38
+ function getTools() {
39
+ if (!shouldExposeTools()) {
40
+ return [];
41
+ }
42
+
43
+ return [
44
+ {
45
+ name: TOOL_NAME,
46
+ description: TOOL_DESCRIPTION,
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ query: {
51
+ type: 'string',
52
+ description:
53
+ 'Web query to resolve through CCS providers. Prefer this tool over ad hoc Bash/curl lookups when you need current web information.',
54
+ },
55
+ },
56
+ required: ['query'],
57
+ additionalProperties: false,
58
+ },
59
+ },
60
+ ];
61
+ }
62
+
63
+ function writeMessage(message) {
64
+ process.stdout.write(`${JSON.stringify(message)}\n`);
65
+ }
66
+
67
+ function writeResponse(id, result) {
68
+ writeMessage({
69
+ jsonrpc: '2.0',
70
+ id,
71
+ result,
72
+ });
73
+ }
74
+
75
+ function writeError(id, code, message) {
76
+ writeMessage({
77
+ jsonrpc: '2.0',
78
+ id,
79
+ error: {
80
+ code,
81
+ message,
82
+ },
83
+ });
84
+ }
85
+
86
+ async function handleToolCall(message) {
87
+ const id = message.id;
88
+ const params = message.params || {};
89
+ const toolArgs = params.arguments || {};
90
+ const toolName = params.name || '<missing>';
91
+ const query = typeof toolArgs.query === 'string' ? toolArgs.query.trim() : '';
92
+ const fingerprint = getQueryFingerprint(query);
93
+
94
+ if (!isSupportedToolName(toolName)) {
95
+ traceWebSearchEvent('mcp_tool_call_rejected', {
96
+ source: 'mcp',
97
+ reason: 'unknown_tool',
98
+ toolName,
99
+ });
100
+ writeError(id, -32602, `Unknown tool: ${toolName}`);
101
+ return;
102
+ }
103
+
104
+ sessionState.toolCalls += 1;
105
+ traceWebSearchEvent('mcp_tool_call_received', {
106
+ source: 'mcp',
107
+ toolName,
108
+ ...fingerprint,
109
+ });
110
+
111
+ if (!shouldExposeTools()) {
112
+ traceWebSearchEvent('mcp_tool_call_unavailable', {
113
+ source: 'mcp',
114
+ toolName,
115
+ exposed: false,
116
+ skipReason: getSkipReason(),
117
+ activeProviderIds: getActiveProviderIds(),
118
+ ...fingerprint,
119
+ });
120
+ writeResponse(id, {
121
+ content: [
122
+ {
123
+ type: 'text',
124
+ text: 'CCS WebSearch is unavailable for this profile or no providers are ready.',
125
+ },
126
+ ],
127
+ isError: true,
128
+ });
129
+ return;
130
+ }
131
+
132
+ if (!query) {
133
+ traceWebSearchEvent('mcp_tool_call_rejected', {
134
+ source: 'mcp',
135
+ reason: 'empty_query',
136
+ toolName,
137
+ });
138
+ writeError(id, -32602, `Tool "${TOOL_NAME}" requires a non-empty string query.`);
139
+ return;
140
+ }
141
+
142
+ const result = await runLocalWebSearch(query);
143
+ if (result.success) {
144
+ traceWebSearchEvent('mcp_tool_call_result', {
145
+ source: 'mcp',
146
+ toolName,
147
+ success: true,
148
+ providerId: result.providerId,
149
+ providerName: result.providerName,
150
+ ...fingerprint,
151
+ });
152
+ writeResponse(id, {
153
+ content: [{ type: 'text', text: result.content }],
154
+ });
155
+ return;
156
+ }
157
+
158
+ traceWebSearchEvent('mcp_tool_call_result', {
159
+ source: 'mcp',
160
+ toolName,
161
+ success: false,
162
+ noActiveProviders: Boolean(result.noActiveProviders),
163
+ errorCount: result.errors.length,
164
+ ...fingerprint,
165
+ });
166
+
167
+ const errorDetail =
168
+ result.noActiveProviders || result.errors.length === 0
169
+ ? 'No active WebSearch providers are ready.'
170
+ : result.errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
171
+
172
+ writeResponse(id, {
173
+ content: [
174
+ {
175
+ type: 'text',
176
+ text: `CCS local WebSearch failed for "${query}". ${errorDetail}`,
177
+ },
178
+ ],
179
+ isError: true,
180
+ });
181
+ }
182
+
183
+ async function handleMessage(message) {
184
+ if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
185
+ return;
186
+ }
187
+
188
+ switch (message.method) {
189
+ case 'initialize':
190
+ sessionState.initializeCount += 1;
191
+ sessionState.exposed = sessionState.exposed || shouldExposeTools();
192
+ traceWebSearchEvent('mcp_initialize', {
193
+ source: 'mcp',
194
+ exposed: shouldExposeTools(),
195
+ skipReason: getSkipReason(),
196
+ activeProviderIds: getActiveProviderIds(),
197
+ });
198
+ writeResponse(message.id, {
199
+ protocolVersion: PROTOCOL_VERSION,
200
+ capabilities: {
201
+ tools: {},
202
+ },
203
+ serverInfo: {
204
+ name: SERVER_NAME,
205
+ version: SERVER_VERSION,
206
+ },
207
+ });
208
+ return;
209
+ case 'notifications/initialized':
210
+ return;
211
+ case 'ping':
212
+ writeResponse(message.id, {});
213
+ return;
214
+ case 'tools/list':
215
+ sessionState.toolsListCount += 1;
216
+ {
217
+ const tools = getTools();
218
+ const exposed = tools.length > 0;
219
+ sessionState.exposed = sessionState.exposed || exposed;
220
+ traceWebSearchEvent('mcp_tools_list', {
221
+ source: 'mcp',
222
+ exposed,
223
+ toolNames: tools.map((tool) => tool.name),
224
+ activeProviderIds: getActiveProviderIds(),
225
+ skipReason: getSkipReason(),
226
+ });
227
+ writeResponse(message.id, { tools });
228
+ }
229
+ return;
230
+ case 'tools/call':
231
+ await handleToolCall(message);
232
+ return;
233
+ default:
234
+ if (message.id !== undefined) {
235
+ writeError(message.id, -32601, `Method not found: ${message.method}`);
236
+ }
237
+ }
238
+ }
239
+
240
+ function writeSessionSummary(exitCodeOrSignal) {
241
+ if (sessionSummaryWritten) {
242
+ return;
243
+ }
244
+
245
+ sessionSummaryWritten = true;
246
+ traceWebSearchEvent('mcp_session_summary', {
247
+ source: 'mcp',
248
+ initializeCount: sessionState.initializeCount,
249
+ toolsListCount: sessionState.toolsListCount,
250
+ exposed: sessionState.exposed,
251
+ toolCalls: sessionState.toolCalls,
252
+ calledWebSearch: sessionState.toolCalls > 0,
253
+ likelyBypassed: sessionState.exposed && sessionState.toolCalls === 0 ? 'unknown' : false,
254
+ activeProviderIds: getActiveProviderIds(),
255
+ skipReason: getSkipReason(),
256
+ exitCode: typeof exitCodeOrSignal === 'number' ? exitCodeOrSignal : null,
257
+ exitSignal: typeof exitCodeOrSignal === 'string' ? exitCodeOrSignal : null,
258
+ });
259
+ }
260
+
261
+ function parseMessages() {
262
+ while (true) {
263
+ let body;
264
+ const startsWithLegacyHeaders = inputBuffer
265
+ .slice(0, Math.min(inputBuffer.length, 32))
266
+ .toString('utf8')
267
+ .toLowerCase()
268
+ .startsWith('content-length:');
269
+
270
+ if (startsWithLegacyHeaders) {
271
+ const headerEnd = inputBuffer.indexOf('\r\n\r\n');
272
+ if (headerEnd === -1) {
273
+ return;
274
+ }
275
+
276
+ const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
277
+ const contentLengthMatch = headerText.match(/content-length:\s*(\d+)/i);
278
+ if (!contentLengthMatch) {
279
+ inputBuffer = Buffer.alloc(0);
280
+ return;
281
+ }
282
+
283
+ const contentLength = Number.parseInt(contentLengthMatch[1], 10);
284
+ const messageEnd = headerEnd + 4 + contentLength;
285
+ if (inputBuffer.length < messageEnd) {
286
+ return;
287
+ }
288
+
289
+ body = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
290
+ inputBuffer = inputBuffer.slice(messageEnd);
291
+ } else {
292
+ const newlineIndex = inputBuffer.indexOf('\n');
293
+ if (newlineIndex === -1) {
294
+ return;
295
+ }
296
+
297
+ body = inputBuffer.slice(0, newlineIndex).toString('utf8').replace(/\r$/, '').trim();
298
+ inputBuffer = inputBuffer.slice(newlineIndex + 1);
299
+ if (!body) {
300
+ continue;
301
+ }
302
+ }
303
+
304
+ let message;
305
+ try {
306
+ message = JSON.parse(body);
307
+ } catch {
308
+ continue;
309
+ }
310
+
311
+ Promise.resolve(handleMessage(message)).catch((error) => {
312
+ if (message && message.id !== undefined) {
313
+ writeError(message.id, -32603, (error && error.message) || 'Internal error');
314
+ }
315
+ });
316
+ }
317
+ }
318
+
319
+ process.stdin.on('data', (chunk) => {
320
+ inputBuffer = Buffer.concat([inputBuffer, chunk]);
321
+ parseMessages();
322
+ });
323
+
324
+ process.stdin.on('error', () => {
325
+ process.exit(0);
326
+ });
327
+
328
+ process.on('exit', (code) => {
329
+ writeSessionSummary(code);
330
+ });
331
+
332
+ ['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
333
+ process.on(signal, () => {
334
+ writeSessionSummary(signal);
335
+ process.exit(0);
336
+ });
337
+ });
338
+
339
+ process.stdin.resume();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "7.63.1",
3
+ "version": "7.64.0-dev.2",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more",
5
5
  "keywords": [
6
6
  "cli",
@@ -81,12 +81,13 @@
81
81
  "test:native": "bash tests/native/unix/edge-cases.sh",
82
82
  "test:e2e": "bun test tests/e2e/ --bail --timeout 60000",
83
83
  "report:hardening": "node scripts/hardening-inventory.js",
84
- "dev": "bun run build:server && bun dist/ccs.js config --dev",
84
+ "dev": "bun run build:server && node dist/ccs.js config --dev",
85
85
  "dev:symlink": "bash scripts/dev-symlink.sh",
86
86
  "dev:unlink": "bash scripts/dev-symlink.sh --restore",
87
87
  "ui:build": "cd ui && bun run build",
88
88
  "ui:preview": "cd ui && bun run preview",
89
89
  "ui:validate": "cd ui && bun run validate",
90
+ "prepack": "bun run build:all",
90
91
  "prepare": "husky",
91
92
  "postinstall": "node scripts/postinstall.js"
92
93
  },
@@ -21,6 +21,12 @@ const STATUS_LABELS = {
21
21
  na: 'N/A',
22
22
  };
23
23
 
24
+ const REVIEW_MODE_DETAILS = {
25
+ fast: 'diff-focused bounded review',
26
+ triage: 'hotspot-based bounded review (non-exhaustive)',
27
+ deep: 'expanded surrounding-code review',
28
+ };
29
+
24
30
  const RENDERER_OWNED_MARKUP_PATTERNS = [
25
31
  { pattern: /^#{1,6}\s/u, reason: 'markdown heading' },
26
32
  { pattern: /^\s*Verdict\s*:/iu, reason: 'verdict label' },
@@ -44,6 +50,171 @@ function renderCode(value) {
44
50
  return `${fence}${text}${fence}`;
45
51
  }
46
52
 
53
+ function parsePositiveInteger(value) {
54
+ if (value === null || value === undefined || value === '') {
55
+ return null;
56
+ }
57
+
58
+ const parsed = typeof value === 'number' ? value : Number.parseInt(cleanText(value), 10);
59
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
60
+ }
61
+
62
+ function normalizeReviewMode(value) {
63
+ const mode = cleanText(value).toLowerCase();
64
+ return REVIEW_MODE_DETAILS[mode] ? mode : null;
65
+ }
66
+
67
+ function normalizeRenderingMetadata(raw) {
68
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
69
+ return {};
70
+ }
71
+
72
+ const mode = normalizeReviewMode(raw.mode);
73
+ const maxTurns = parsePositiveInteger(raw.maxTurns);
74
+ const timeoutMinutes = parsePositiveInteger(raw.timeoutMinutes);
75
+ const timeoutSeconds = parsePositiveInteger(raw.timeoutSeconds);
76
+ const selectedFiles = parsePositiveInteger(raw.selectedFiles);
77
+ const reviewableFiles = parsePositiveInteger(raw.reviewableFiles);
78
+ const selectedChanges = parsePositiveInteger(raw.selectedChanges);
79
+ const reviewableChanges = parsePositiveInteger(raw.reviewableChanges);
80
+ const scopeLabel = cleanText(raw.scopeLabel).toLowerCase();
81
+ const metadata = {};
82
+
83
+ if (mode) metadata.mode = mode;
84
+ if (maxTurns) metadata.maxTurns = maxTurns;
85
+ if (timeoutMinutes) metadata.timeoutMinutes = timeoutMinutes;
86
+ if (timeoutSeconds) metadata.timeoutSeconds = timeoutSeconds;
87
+ if (selectedFiles) metadata.selectedFiles = selectedFiles;
88
+ if (reviewableFiles) metadata.reviewableFiles = reviewableFiles;
89
+ if (selectedChanges) metadata.selectedChanges = selectedChanges;
90
+ if (reviewableChanges) metadata.reviewableChanges = reviewableChanges;
91
+ if (scopeLabel === 'reviewable files' || scopeLabel === 'changed files') metadata.scopeLabel = scopeLabel;
92
+
93
+ return metadata;
94
+ }
95
+
96
+ function mergeRenderingMetadata(...sources) {
97
+ const merged = {};
98
+ for (const source of sources) {
99
+ Object.assign(merged, normalizeRenderingMetadata(source));
100
+ }
101
+ return merged;
102
+ }
103
+
104
+ function formatTurnBudget(rendering) {
105
+ return typeof rendering.maxTurns === 'number' ? `${rendering.maxTurns} turns` : null;
106
+ }
107
+
108
+ function formatTimeBudget(rendering) {
109
+ if (typeof rendering.timeoutMinutes === 'number') {
110
+ return `${rendering.timeoutMinutes} minute${rendering.timeoutMinutes === 1 ? '' : 's'}`;
111
+ }
112
+
113
+ if (typeof rendering.timeoutSeconds === 'number') {
114
+ return `${rendering.timeoutSeconds} second${rendering.timeoutSeconds === 1 ? '' : 's'}`;
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ function formatCombinedBudget(rendering) {
121
+ const parts = [formatTurnBudget(rendering), formatTimeBudget(rendering)].filter(Boolean);
122
+ return parts.length > 0 ? parts.join(' / ') : null;
123
+ }
124
+
125
+ function formatScopeSummary(rendering) {
126
+ if (
127
+ typeof rendering.selectedFiles !== 'number' ||
128
+ typeof rendering.reviewableFiles !== 'number'
129
+ ) {
130
+ return null;
131
+ }
132
+
133
+ const scopeLabel = rendering.scopeLabel || 'reviewable files';
134
+ const fileScope = `${rendering.selectedFiles}/${rendering.reviewableFiles} ${scopeLabel}`;
135
+ if (
136
+ typeof rendering.selectedChanges === 'number' &&
137
+ typeof rendering.reviewableChanges === 'number'
138
+ ) {
139
+ const changeLabel = scopeLabel === 'reviewable files' ? 'reviewable changed lines' : 'changed lines';
140
+ return `${fileScope}; ${rendering.selectedChanges}/${rendering.reviewableChanges} ${changeLabel}`;
141
+ }
142
+
143
+ return fileScope;
144
+ }
145
+
146
+ function formatReviewContext(rendering) {
147
+ const parts = [];
148
+
149
+ if (rendering.mode) {
150
+ parts.push(`mode ${renderCode(rendering.mode)}`);
151
+ parts.push(REVIEW_MODE_DETAILS[rendering.mode]);
152
+ }
153
+
154
+ const scopeSummary = formatScopeSummary(rendering);
155
+ if (scopeSummary) {
156
+ parts.push(`scope ${scopeSummary}`);
157
+ }
158
+
159
+ const turnBudget = formatTurnBudget(rendering);
160
+ if (turnBudget) {
161
+ parts.push(`turn budget ${turnBudget}`);
162
+ }
163
+
164
+ const timeBudget = formatTimeBudget(rendering);
165
+ if (timeBudget) {
166
+ parts.push(`workflow cap ${timeBudget}`);
167
+ }
168
+
169
+ if (parts.length === 0) {
170
+ return null;
171
+ }
172
+
173
+ return `> 🧭 Review context: ${parts.join('; ')}.`;
174
+ }
175
+
176
+ function classifyFallbackReason(reason) {
177
+ const normalized = cleanText(reason).toLowerCase();
178
+ if (!normalized || normalized === 'missing structured output') {
179
+ return 'missing';
180
+ }
181
+
182
+ if (normalized === 'structured output is not valid json') {
183
+ return 'invalid_json';
184
+ }
185
+
186
+ return 'invalid_fields';
187
+ }
188
+
189
+ function describeIncompleteOutcome({ reason, rendering, turnsUsed, status }) {
190
+ const reviewLabel = rendering.mode ? `${renderCode(rendering.mode)} review` : 'bounded review';
191
+ const turnBudget = formatTurnBudget(rendering);
192
+ const timeBudget = formatTimeBudget(rendering);
193
+ const combinedBudget = formatCombinedBudget(rendering);
194
+ const exhaustedTurnBudget =
195
+ typeof turnsUsed === 'number' &&
196
+ typeof rendering.maxTurns === 'number' &&
197
+ turnsUsed >= rendering.maxTurns;
198
+
199
+ if (status === 'cancelled' && timeBudget) {
200
+ return `The ${reviewLabel} hit the workflow runtime cap before it produced validated structured output. The run stayed bounded to ${timeBudget}.`;
201
+ }
202
+
203
+ if (exhaustedTurnBudget) {
204
+ return `The ${reviewLabel} reached its ${rendering.maxTurns}-turn runtime budget before it produced validated structured output.`;
205
+ }
206
+
207
+ if (combinedBudget && classifyFallbackReason(reason) === 'missing') {
208
+ return `The ${reviewLabel} ended before it could produce validated structured output within the available ${combinedBudget} runtime budget.`;
209
+ }
210
+
211
+ if (classifyFallbackReason(reason) === 'missing' || classifyFallbackReason(reason) === 'invalid_json') {
212
+ return `The ${reviewLabel} ended without validated structured output, so the normalizer published the safe fallback comment instead.`;
213
+ }
214
+
215
+ return `The ${reviewLabel} returned incomplete structured data, so the normalizer published the safe fallback comment instead.`;
216
+ }
217
+
47
218
  function validatePlainTextField(fieldName, value) {
48
219
  const text = cleanText(value);
49
220
  if (!text) {
@@ -155,6 +326,8 @@ export function normalizeStructuredOutput(raw) {
155
326
  const strengths = normalizeStringList('strengths', parsed.strengths);
156
327
  if (!strengths.ok) return strengths;
157
328
 
329
+ const rendering = normalizeRenderingMetadata(parsed.rendering);
330
+
158
331
  if (!ASSESSMENTS[overallAssessment] || findings === null) {
159
332
  return { ok: false, reason: 'structured output is missing required review fields' };
160
333
  }
@@ -203,19 +376,22 @@ export function normalizeStructuredOutput(raw) {
203
376
  });
204
377
  }
205
378
 
206
- return {
207
- ok: true,
208
- value: {
209
- summary: summary.value,
210
- findings: normalizedFindings,
211
- overallAssessment,
212
- overallRationale: overallRationale.value,
213
- securityChecklist: securityChecklist.value,
214
- ccsCompliance: ccsCompliance.value,
215
- informational: informational.value,
216
- strengths: strengths.value,
217
- },
379
+ const value = {
380
+ summary: summary.value,
381
+ findings: normalizedFindings,
382
+ overallAssessment,
383
+ overallRationale: overallRationale.value,
384
+ securityChecklist: securityChecklist.value,
385
+ ccsCompliance: ccsCompliance.value,
386
+ informational: informational.value,
387
+ strengths: strengths.value,
218
388
  };
389
+
390
+ if (Object.keys(rendering).length > 0) {
391
+ value.rendering = rendering;
392
+ }
393
+
394
+ return { ok: true, value };
219
395
  }
220
396
 
221
397
  function renderChecklistTable(title, labelHeader, labelKey, rows) {
@@ -233,8 +409,14 @@ function renderBulletSection(title, items) {
233
409
  return ['', title, ...items.map((item) => `- ${escapeMarkdownText(item)}`)];
234
410
  }
235
411
 
236
- export function renderStructuredReview(review, { model }) {
412
+ export function renderStructuredReview(review, { model, rendering: renderOptions } = {}) {
413
+ const rendering = mergeRenderingMetadata(review?.rendering, renderOptions);
237
414
  const lines = ['### 📋 Summary', '', escapeMarkdownText(review.summary), '', '### 🔍 Findings'];
415
+ const reviewContext = formatReviewContext(rendering);
416
+
417
+ if (reviewContext) {
418
+ lines.splice(4, 0, reviewContext, '');
419
+ }
238
420
 
239
421
  if (review.findings.length === 0) {
240
422
  lines.push('No confirmed issues found after reviewing the diff and surrounding code.');
@@ -273,15 +455,35 @@ export function renderStructuredReview(review, { model }) {
273
455
  return lines.join('\n');
274
456
  }
275
457
 
276
- export function renderIncompleteReview({ model, reason, runUrl, runtimeTools, turnsUsed }) {
458
+ export function renderIncompleteReview({
459
+ model,
460
+ reason,
461
+ runUrl,
462
+ runtimeTools,
463
+ turnsUsed,
464
+ rendering: renderOptions,
465
+ status,
466
+ }) {
467
+ const rendering = mergeRenderingMetadata(renderOptions);
277
468
  const lines = [
278
469
  '### ⚠️ AI Review Incomplete',
279
470
  '',
280
471
  'Claude did not return validated structured review output, so this workflow did not publish raw scratch text.',
281
472
  '',
282
- `- Reason: ${escapeMarkdownText(reason)}`,
473
+ `- Outcome: ${describeIncompleteOutcome({ reason, rendering, turnsUsed, status })}`,
283
474
  ];
284
475
 
476
+ if (rendering.mode) {
477
+ lines.push(`- Review mode: ${renderCode(rendering.mode)} (${escapeMarkdownText(REVIEW_MODE_DETAILS[rendering.mode])})`);
478
+ }
479
+ const scopeSummary = formatScopeSummary(rendering);
480
+ if (scopeSummary) {
481
+ lines.push(`- Review scope: ${escapeMarkdownText(scopeSummary)}`);
482
+ }
483
+ const runtimeBudget = formatCombinedBudget(rendering);
484
+ if (runtimeBudget) {
485
+ lines.push(`- Runtime budget: ${escapeMarkdownText(runtimeBudget)}`);
486
+ }
285
487
  if (runtimeTools?.length) {
286
488
  lines.push(`- Runtime tools: ${runtimeTools.map(renderCode).join(', ')}`);
287
489
  }
@@ -299,14 +501,28 @@ export function writeReviewFromEnv(env = process.env) {
299
501
  const runUrl = env.AI_REVIEW_RUN_URL || '#';
300
502
  const validation = normalizeStructuredOutput(env.AI_REVIEW_STRUCTURED_OUTPUT);
301
503
  const metadata = readExecutionMetadata(env.AI_REVIEW_EXECUTION_FILE);
504
+ const status = cleanText(env.AI_REVIEW_STATUS).toLowerCase() || null;
505
+ const rendering = normalizeRenderingMetadata({
506
+ mode: env.AI_REVIEW_MODE,
507
+ selectedFiles: env.AI_REVIEW_SELECTED_FILES,
508
+ reviewableFiles: env.AI_REVIEW_REVIEWABLE_FILES,
509
+ selectedChanges: env.AI_REVIEW_SELECTED_CHANGES,
510
+ reviewableChanges: env.AI_REVIEW_REVIEWABLE_CHANGES,
511
+ scopeLabel: env.AI_REVIEW_SCOPE_LABEL,
512
+ maxTurns: env.AI_REVIEW_MAX_TURNS,
513
+ timeoutMinutes: env.AI_REVIEW_TIMEOUT_MINUTES ?? env.AI_REVIEW_TIMEOUT_MINUTES_BUDGET,
514
+ timeoutSeconds: env.AI_REVIEW_TIMEOUT_SECONDS ?? env.AI_REVIEW_TIMEOUT_SEC,
515
+ });
302
516
  const content = validation.ok
303
- ? renderStructuredReview(validation.value, { model })
517
+ ? renderStructuredReview(validation.value, { model, rendering })
304
518
  : renderIncompleteReview({
305
519
  model,
306
520
  reason: validation.reason,
307
521
  runUrl,
308
522
  runtimeTools: metadata.runtimeTools,
309
523
  turnsUsed: metadata.turnsUsed,
524
+ rendering,
525
+ status,
310
526
  });
311
527
 
312
528
  fs.mkdirSync(path.dirname(outputFile), { recursive: true });