@jsonstudio/rcc 0.89.2202 → 0.90.1

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 (260) hide show
  1. package/README.md +27 -0
  2. package/dist/build-info.js +2 -2
  3. package/dist/build-info.js.map +1 -1
  4. package/dist/cli/commands/claude.js +1 -0
  5. package/dist/cli/commands/claude.js.map +1 -1
  6. package/dist/cli/commands/codex.js +1 -0
  7. package/dist/cli/commands/codex.js.map +1 -1
  8. package/dist/cli/commands/guardian-daemon.d.ts +2 -0
  9. package/dist/cli/commands/guardian-daemon.js +299 -0
  10. package/dist/cli/commands/guardian-daemon.js.map +1 -0
  11. package/dist/cli/commands/init/camoufox.js +1 -1
  12. package/dist/cli/commands/init/camoufox.js.map +1 -1
  13. package/dist/cli/commands/init.js +15 -1
  14. package/dist/cli/commands/init.js.map +1 -1
  15. package/dist/cli/commands/launcher/types.d.ts +6 -0
  16. package/dist/cli/commands/launcher-kernel.js +456 -109
  17. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  18. package/dist/cli/commands/port.js +28 -8
  19. package/dist/cli/commands/port.js.map +1 -1
  20. package/dist/cli/commands/restart.d.ts +4 -0
  21. package/dist/cli/commands/restart.js +91 -42
  22. package/dist/cli/commands/restart.js.map +1 -1
  23. package/dist/cli/commands/start-types.d.ts +4 -0
  24. package/dist/cli/commands/start.js +112 -68
  25. package/dist/cli/commands/start.js.map +1 -1
  26. package/dist/cli/commands/stop.d.ts +3 -0
  27. package/dist/cli/commands/stop.js +30 -63
  28. package/dist/cli/commands/stop.js.map +1 -1
  29. package/dist/cli/config/init-config.js +15 -1
  30. package/dist/cli/config/init-config.js.map +1 -1
  31. package/dist/cli/config/init-provider-catalog.js +13 -5
  32. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  33. package/dist/cli/guardian/client.d.ts +38 -0
  34. package/dist/cli/guardian/client.js +237 -0
  35. package/dist/cli/guardian/client.js.map +1 -0
  36. package/dist/cli/guardian/paths.d.ts +7 -0
  37. package/dist/cli/guardian/paths.js +13 -0
  38. package/dist/cli/guardian/paths.js.map +1 -0
  39. package/dist/cli/guardian/types.d.ts +30 -0
  40. package/dist/cli/guardian/types.js +2 -0
  41. package/dist/cli/guardian/types.js.map +1 -0
  42. package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
  43. package/dist/cli/register/guardian-daemon-command.js +5 -0
  44. package/dist/cli/register/guardian-daemon-command.js.map +1 -0
  45. package/dist/cli/server/port-utils.js +57 -1
  46. package/dist/cli/server/port-utils.js.map +1 -1
  47. package/dist/cli.js +48 -0
  48. package/dist/cli.js.map +1 -1
  49. package/dist/commands/oauth.js +6 -6
  50. package/dist/commands/oauth.js.map +1 -1
  51. package/dist/commands/provider-update.js +12 -0
  52. package/dist/commands/provider-update.js.map +1 -1
  53. package/dist/config/routecodex-config-loader.js +66 -1
  54. package/dist/config/routecodex-config-loader.js.map +1 -1
  55. package/dist/config/virtual-router-builder.js +18 -0
  56. package/dist/config/virtual-router-builder.js.map +1 -1
  57. package/dist/config/virtual-router-types.js +20 -5
  58. package/dist/config/virtual-router-types.js.map +1 -1
  59. package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
  60. package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
  61. package/dist/daemon-admin-ui/index.html +13 -0
  62. package/dist/docs/daemon-admin-ui.html +328 -57
  63. package/dist/index.d.ts +9 -0
  64. package/dist/index.js +268 -10
  65. package/dist/index.js.map +1 -1
  66. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
  67. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
  68. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
  69. package/dist/manager/modules/quota/provider-quota-daemon.events.js +50 -1
  70. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  71. package/dist/providers/auth/antigravity-user-agent.js +78 -31
  72. package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
  73. package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
  74. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  75. package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
  76. package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
  77. package/dist/providers/auth/oauth-error-message.d.ts +1 -0
  78. package/dist/providers/auth/oauth-error-message.js +44 -0
  79. package/dist/providers/auth/oauth-error-message.js.map +1 -0
  80. package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
  81. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
  82. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
  83. package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
  84. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
  85. package/dist/providers/auth/oauth-lifecycle.js +502 -87
  86. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  87. package/dist/providers/auth/oauth-repair-cooldown.js +2 -7
  88. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  89. package/dist/providers/auth/oauth-repair-env.js +3 -5
  90. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  91. package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
  92. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
  93. package/dist/providers/auth/qwen-userinfo-helper.js +18 -3
  94. package/dist/providers/auth/qwen-userinfo-helper.js.map +1 -1
  95. package/dist/providers/auth/tokenfile-auth.d.ts +1 -0
  96. package/dist/providers/auth/tokenfile-auth.js +15 -9
  97. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  98. package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
  99. package/dist/providers/core/config/camoufox-actions.js +461 -0
  100. package/dist/providers/core/config/camoufox-actions.js.map +1 -0
  101. package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
  102. package/dist/providers/core/config/camoufox-launcher.js +518 -160
  103. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  104. package/dist/providers/core/config/oauth-flows.js +6 -44
  105. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  106. package/dist/providers/core/config/provider-oauth-configs.js +51 -7
  107. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  108. package/dist/providers/core/config/service-profiles.js +13 -4
  109. package/dist/providers/core/config/service-profiles.js.map +1 -1
  110. package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
  111. package/dist/providers/core/runtime/http-transport-provider.js +60 -1
  112. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  113. package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
  114. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  115. package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
  116. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
  117. package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
  118. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
  119. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  120. package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
  121. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  122. package/dist/providers/core/strategies/oauth-device-flow.js +6 -3
  123. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  124. package/dist/providers/profile/families/iflow-profile.js +83 -10
  125. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  126. package/dist/providers/profile/families/qwen-profile.js +203 -0
  127. package/dist/providers/profile/families/qwen-profile.js.map +1 -1
  128. package/dist/scripts/camoufox/launch-auth.mjs +112 -5
  129. package/dist/server/handlers/config-admin-handler.js +9 -2
  130. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  131. package/dist/server/handlers/handler-utils.js +3 -14
  132. package/dist/server/handlers/handler-utils.js.map +1 -1
  133. package/dist/server/handlers/logging.js +3 -4
  134. package/dist/server/handlers/logging.js.map +1 -1
  135. package/dist/server/runtime/http-server/clock-client-reaper.d.ts +1 -0
  136. package/dist/server/runtime/http-server/clock-client-reaper.js +21 -15
  137. package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -1
  138. package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +4 -0
  139. package/dist/server/runtime/http-server/clock-client-registry-utils.js +74 -16
  140. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -1
  141. package/dist/server/runtime/http-server/clock-client-registry.d.ts +15 -0
  142. package/dist/server/runtime/http-server/clock-client-registry.js +300 -6
  143. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
  144. package/dist/server/runtime/http-server/clock-client-routes.js +49 -19
  145. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
  146. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +16 -0
  147. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +49 -0
  148. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -1
  149. package/dist/server/runtime/http-server/clock-scope-resolution.d.ts +14 -0
  150. package/dist/server/runtime/http-server/clock-scope-resolution.js +212 -0
  151. package/dist/server/runtime/http-server/clock-scope-resolution.js.map +1 -0
  152. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
  153. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  154. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
  155. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  156. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
  157. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  158. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
  159. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
  160. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  161. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
  162. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  163. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +18 -29
  164. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  165. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
  166. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  167. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
  168. package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
  169. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  170. package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
  171. package/dist/server/runtime/http-server/executor/client-injection-flow.js +287 -0
  172. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
  173. package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
  174. package/dist/server/runtime/http-server/executor/index.js +1 -1
  175. package/dist/server/runtime/http-server/executor/index.js.map +1 -1
  176. package/dist/server/runtime/http-server/executor/provider-response-converter.js +236 -62
  177. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  178. package/dist/server/runtime/http-server/executor/provider-response-utils.js +5 -0
  179. package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
  180. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +2 -0
  181. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +60 -0
  182. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
  183. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +20 -8
  184. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  185. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
  186. package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
  187. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
  188. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
  189. package/dist/server/runtime/http-server/executor/usage-aggregator.js +84 -88
  190. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  191. package/dist/server/runtime/http-server/executor-metadata.js +328 -7
  192. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  193. package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
  194. package/dist/server/runtime/http-server/executor-response.js +52 -50
  195. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  196. package/dist/server/runtime/http-server/http-server-bootstrap.js +55 -6
  197. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  198. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +1 -0
  199. package/dist/server/runtime/http-server/http-server-clock-daemon.js +199 -44
  200. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -1
  201. package/dist/server/runtime/http-server/http-server-lifecycle.js +4 -4
  202. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  203. package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
  204. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  205. package/dist/server/runtime/http-server/index.d.ts +1 -0
  206. package/dist/server/runtime/http-server/index.js +1 -0
  207. package/dist/server/runtime/http-server/index.js.map +1 -1
  208. package/dist/server/runtime/http-server/middleware.js +82 -4
  209. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  210. package/dist/server/runtime/http-server/request-executor.js +26 -7
  211. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  212. package/dist/server/runtime/http-server/routes.d.ts +2 -1
  213. package/dist/server/runtime/http-server/routes.js +4 -2
  214. package/dist/server/runtime/http-server/routes.js.map +1 -1
  215. package/dist/server/runtime/http-server/session-dir.js +12 -1
  216. package/dist/server/runtime/http-server/session-dir.js.map +1 -1
  217. package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
  218. package/dist/server/runtime/http-server/stats-manager.js +269 -21
  219. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  220. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +13 -0
  221. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +168 -0
  222. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
  223. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
  224. package/dist/server/runtime/http-server/tmux-session-probe.js +97 -0
  225. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  226. package/dist/server-lifecycle/port-utils.d.ts +2 -1
  227. package/dist/server-lifecycle/port-utils.js +84 -4
  228. package/dist/server-lifecycle/port-utils.js.map +1 -1
  229. package/dist/token-daemon/index.d.ts +1 -0
  230. package/dist/token-daemon/index.js +17 -12
  231. package/dist/token-daemon/index.js.map +1 -1
  232. package/dist/utils/clock-client-token.d.ts +2 -1
  233. package/dist/utils/clock-client-token.js +52 -8
  234. package/dist/utils/clock-client-token.js.map +1 -1
  235. package/dist/utils/clock-scope-trace.d.ts +11 -0
  236. package/dist/utils/clock-scope-trace.js +41 -0
  237. package/dist/utils/clock-scope-trace.js.map +1 -0
  238. package/dist/utils/llms-engine-shadow.js +1 -1
  239. package/dist/utils/llms-engine-shadow.js.map +1 -1
  240. package/docs/DAEMON_CONTROL_PLANE.md +1 -0
  241. package/docs/ROUTING_POLICY_SCHEMA.md +4 -2
  242. package/docs/daemon-admin-ui.html +328 -57
  243. package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
  244. package/docs/exec-command-guard-policy.example.v1.json +7 -1
  245. package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
  246. package/package.json +23 -6
  247. package/scripts/build-core.mjs +12 -0
  248. package/scripts/camoufox/launch-auth.mjs +112 -5
  249. package/scripts/ci/repo-sanity.mjs +1 -0
  250. package/scripts/cleanup-stale-server-pids.mjs +142 -0
  251. package/scripts/install-global.sh +8 -0
  252. package/scripts/install-verify.mjs +33 -16
  253. package/scripts/run-bg.sh +226 -43
  254. package/scripts/run-fg-gtimeout.sh +158 -14
  255. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
  256. package/scripts/tests/ci-jest.mjs +9 -1
  257. package/scripts/triage-errorsamples.mjs +216 -0
  258. package/scripts/verify-codex-error-samples.mjs +92 -15
  259. package/scripts/verify-e2e-toolcall.mjs +12 -1
  260. package/scripts/verify-install-e2e.mjs +69 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/rcc",
3
- "version": "0.89.2202",
3
+ "version": "0.90.001",
4
4
  "description": "Multi-provider OpenAI proxy server with anthropic/responses/chat support (release)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,9 +29,11 @@
29
29
  "node": ">=20 <26"
30
30
  },
31
31
  "scripts": {
32
- "build": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs && npm run fix:cli-permission",
33
- "build:dev": "bash -lc 'export BUILD_MODE=dev; npm run build && npm run fix:cli-permission && npm run test:unified-hub-shadow && npm run verify:e2e-toolcall && npm run verify:apply-patch && npm run verify:apply-patch-regressions && npm run verify:exec-command && npm run test:routing-instructions && npm run test:cli && npm run install:global && npm run mock:regressions && npm run test:blackbox-antigravity-parity && npm run verify:errorsamples && npm run verify:e2e-gemini-followup-sample'",
34
- "build:min": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs && npm run fix:cli-permission",
32
+ "build": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && npm run build:webui && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs && npm run fix:cli-permission",
33
+ "build:dev": "bash -lc 'export BUILD_MODE=dev ROUTECODEX_BUILD_RESTART_ONLY=1; npm run build && npm run fix:cli-permission && npm run install:global'",
34
+ "build:dev:full": "bash -lc 'export BUILD_MODE=dev ROUTECODEX_BUILD_RESTART_ONLY=1; npm run cleanup:stale-server-pids || true; trap \"npm run cleanup:stale-server-pids >/dev/null 2>&1 || true\" EXIT; npm run build && npm run fix:cli-permission && npm run test:unified-hub-shadow && npm run verify:e2e-toolcall && npm run verify:apply-patch && npm run verify:apply-patch-regressions && npm run verify:exec-command && npm run test:routing-instructions && npm run test:cli && npm run install:global && npm run mock:regressions && npm run test:blackbox-antigravity-parity && npm run verify:errorsamples && npm run verify:e2e-gemini-followup-sample'",
35
+ "build:min": "npm run llmswitch:ensure && node scripts/build-core.mjs && node scripts/vendor-core.mjs && npm run clean && node scripts/gen-build-info.mjs && tsc && npm run build:webui && node scripts/copy-compat-assets.mjs && node scripts/copy-modules-config.mjs && npm run fix:cli-permission",
36
+ "build:webui": "vite build --config webui/vite.config.ts",
35
37
  "prepack": "echo skip-prepack",
36
38
  "postbuild": "node scripts/ensure-cli-executable.mjs",
37
39
  "build:watch": "tsc --watch",
@@ -39,6 +41,7 @@
39
41
  "dev": "tsx watch src/index.ts",
40
42
  "jest:run": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
41
43
  "test": "npm run test:routing-instructions && npm run mock:regressions",
44
+ "test:regressions": "npm run test:unified-hub-shadow && npm run mock:regressions && npm run test:blackbox-antigravity-parity && npm run verify:errorsamples",
42
45
  "test:blackbox-antigravity-parity": "node scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs",
43
46
  "test:ci:jest": "node scripts/tests/ci-jest.mjs",
44
47
  "test:ci:jest:coverage": "node scripts/tests/ci-jest.mjs --coverage",
@@ -53,6 +56,8 @@
53
56
  "test:coverage": "npm run jest:run -- --coverage",
54
57
  "test:integration": "npm run jest:run -- --testPathPattern=integration",
55
58
  "test:e2e": "npm run jest:run -- --testPathPattern=e2e",
59
+ "test:webui": "npm run jest:run -- --runTestsByPath tests/frontend/webui-app.utils.spec.ts tests/frontend/webui-app.render.spec.tsx tests/frontend/webui-app.integration.spec.tsx tests/frontend/webui-app.pages.spec.tsx tests/frontend/webui-app.edge.spec.tsx",
60
+ "test:webui:coverage": "npm run jest:run -- --runTestsByPath tests/frontend/webui-app.utils.spec.ts tests/frontend/webui-app.render.spec.tsx tests/frontend/webui-app.integration.spec.tsx tests/frontend/webui-app.pages.spec.tsx tests/frontend/webui-app.edge.spec.tsx --coverage --collectCoverageFrom=webui/src/App.tsx",
56
61
  "test:performance": "npm run jest:run -- --testPathPattern=performance",
57
62
  "test:protocol": "npm run jest:run -- --testPathPattern=protocol-tools-e2e.spec.ts --runInBand --detectOpenHandles --forceExit",
58
63
  "test:dry-run": "node tests/basic-dry-run.mjs",
@@ -63,6 +68,7 @@
63
68
  "lint:fix": "ESLINT_USE_FLAT_CONFIG=1 eslint \"src/**/*.ts\" --fix --no-cache",
64
69
  "lint:strict": "ESLINT_USE_FLAT_CONFIG=1 eslint \"src/**/*.ts\" --max-warnings 0 --no-cache",
65
70
  "clean": "rm -rf dist coverage",
71
+ "cleanup:stale-server-pids": "node scripts/cleanup-stale-server-pids.mjs --quiet",
66
72
  "prebuild": "npm run verify:repo-sanity && echo skip-lint",
67
73
  "prepare": "",
68
74
  "postinstall": "node scripts/ensure-cli-executable.mjs",
@@ -70,6 +76,7 @@
70
76
  "verify:apply-patch-regressions": "node scripts/verify-apply-patch-regressions.mjs",
71
77
  "verify:exec-command": "node scripts/tests/exec-command-loop.mjs",
72
78
  "verify:errorsamples": "node scripts/verify-codex-error-samples.mjs",
79
+ "triage:errorsamples": "node scripts/triage-errorsamples.mjs",
73
80
  "verify:e2e-gemini-followup-sample": "node scripts/verify-e2e-gemini-followup-sample.mjs",
74
81
  "install:global": "./scripts/install-global.sh",
75
82
  "install:release": "./scripts/install-release.sh",
@@ -150,7 +157,7 @@
150
157
  },
151
158
  "dependencies": {
152
159
  "@anthropic-ai/sdk": "^0.65.0",
153
- "@jsonstudio/llms": "^0.6.2125",
160
+ "@jsonstudio/llms": "^0.6.2172",
154
161
  "@lmstudio/sdk": "^1.5.0",
155
162
  "@radix-ui/react-switch": "^1.2.6",
156
163
  "@types/socket.io": "^3.0.1",
@@ -187,28 +194,38 @@
187
194
  },
188
195
  "devDependencies": {
189
196
  "@jest/globals": "^30.1.2",
197
+ "@testing-library/jest-dom": "^6.9.1",
198
+ "@testing-library/react": "^16.3.2",
199
+ "@testing-library/user-event": "^14.6.1",
190
200
  "@types/cors": "^2.8.13",
191
201
  "@types/express": "^4.17.17",
192
202
  "@types/jest": "^29.5.5",
193
203
  "@types/node": "^20.19.25",
204
+ "@types/react": "^19.2.2",
205
+ "@types/react-dom": "^19.2.2",
194
206
  "@typescript-eslint/eslint-plugin": "^6.7.4",
195
207
  "@typescript-eslint/parser": "^6.7.4",
196
208
  "@typescript-eslint/typescript-estree": "^6.7.4",
209
+ "@vitejs/plugin-react": "^5.1.0",
197
210
  "complexity-report": "^2.0.0-alpha",
198
211
  "depcheck": "^1.4.7",
199
212
  "eslint": "^8.50.0",
200
213
  "eslint-plugin-complexity": "^1.0.2",
201
214
  "husky": "^9.0.0",
202
215
  "jest": "^29.7.0",
216
+ "jest-environment-jsdom": "^30.2.0",
203
217
  "jscpd": "^4.0.5",
204
218
  "lint-staged": "^15.0.0",
205
219
  "npm-check-updates": "^18.3.0",
206
220
  "package-json-validator": "^0.30.0",
207
221
  "prettier": "^3.6.2",
222
+ "react": "^19.2.0",
223
+ "react-dom": "^19.2.0",
208
224
  "ts-jest": "^29.1.1",
209
225
  "ts-node": "^10.9.2",
210
226
  "tsx": "^4.0.0",
211
- "typescript": "^5.9.3"
227
+ "typescript": "^5.9.3",
228
+ "vite": "^7.1.12"
212
229
  },
213
230
  "bundleDependencies": []
214
231
  }
@@ -10,6 +10,7 @@ const buildMode = buildModeRaw === 'dev' ? 'dev' : 'release';
10
10
  const tsc = path.join(root, 'node_modules', 'typescript', 'bin', 'tsc');
11
11
  const proj = path.join(root, 'sharedmodule', 'llmswitch-core', 'tsconfig.json');
12
12
  const coreRoot = path.join(root, 'sharedmodule', 'llmswitch-core');
13
+ const nativeBuildScript = path.join(coreRoot, 'scripts', 'build-native-hotpath.mjs');
13
14
  const outDir = path.join(coreRoot, 'dist');
14
15
  const requiredOutputs = [
15
16
  path.join(outDir, 'bridge', 'routecodex-adapter.js'),
@@ -75,6 +76,16 @@ function shouldSkipBuild() {
75
76
  return distMtime >= srcMtime;
76
77
  }
77
78
 
79
+ function runNativeBuild() {
80
+ if (!fs.existsSync(nativeBuildScript)) {
81
+ fail(`native build script missing: ${nativeBuildScript}`);
82
+ }
83
+ const res = spawnSync(process.execPath, [nativeBuildScript], { stdio: 'inherit', cwd: coreRoot });
84
+ if ((res.status ?? 0) !== 0) {
85
+ fail('native build failed for llmswitch-core');
86
+ }
87
+ }
88
+
78
89
  if (!fs.existsSync(tsc)) fail('TypeScript not installed in root node_modules. Run npm i.');
79
90
  if (!fs.existsSync(proj)) {
80
91
  console.log('[build-core] llmswitch-core source not found under sharedmodule; skip local core build (依赖包将用于运行/打包)');
@@ -92,6 +103,7 @@ if (skip === '1' || skip === 'true' || skip === 'yes') {
92
103
  console.log('[build-core] skip requested by env (ROUTECODEX_SKIP_CORE_BUILD/SKIP_CORE_BUILD)');
93
104
  process.exit(0);
94
105
  }
106
+ runNativeBuild();
95
107
  if (shouldSkipBuild()) {
96
108
  console.log('[build-core] dist up-to-date; skip rebuild:', outDir);
97
109
  process.exit(0);
@@ -13,6 +13,30 @@ function isTruthy(value) {
13
13
  return v === '1' || v === 'true' || v === 'yes' || v === 'on';
14
14
  }
15
15
 
16
+ function isFalsy(value) {
17
+ if (!value) return false;
18
+ const v = String(value).trim().toLowerCase();
19
+ return v === '0' || v === 'false' || v === 'no' || v === 'off';
20
+ }
21
+
22
+ function resolveIflowHeadlessMode(devMode) {
23
+ if (devMode) {
24
+ return false;
25
+ }
26
+ const raw = String(
27
+ process.env.ROUTECODEX_CAMOUFOX_IFLOW_HEADLESS ||
28
+ process.env.RCC_CAMOUFOX_IFLOW_HEADLESS ||
29
+ ''
30
+ ).trim();
31
+ if (!raw) {
32
+ return false;
33
+ }
34
+ if (isFalsy(raw)) {
35
+ return false;
36
+ }
37
+ return isTruthy(raw);
38
+ }
39
+
16
40
  function parseArgs(argv) {
17
41
  const args = { profile: 'default', url: '', autoMode: '', devMode: false };
18
42
  const list = argv.slice(2);
@@ -260,13 +284,44 @@ function cleanupExistingCamoufox(profileDir) {
260
284
  if (!profileDir) {
261
285
  return;
262
286
  }
263
- console.log('[camoufox-launch-auth] Ensuring Camoufox profile is clean before launch...');
264
- const pkillArgs = ['-f', profileDir];
287
+ // 清理已知进程,不使用 pkill 普杀
288
+ // 使用 pgrep 查找匹配的进程,然后逐个验证后终止
265
289
  try {
266
- spawnSync('pkill', pkillArgs, { stdio: 'ignore' });
290
+ const probe = spawnSync('pgrep', ['-f', profileDir], {
291
+ encoding: 'utf8',
292
+ stdio: ['ignore', 'pipe', 'ignore']
293
+ });
294
+
295
+ if (probe.status === 0 && probe.stdout) {
296
+ const selfPid = process.pid;
297
+ const lines = String(probe.stdout).split(/\r?\n/).filter(Boolean);
298
+
299
+ for (const line of lines) {
300
+ const pid = Number.parseInt(line.trim(), 10);
301
+ if (!Number.isFinite(pid) || pid <= 0 || pid === selfPid) {
302
+ continue;
303
+ }
304
+ // 验证进程命令包含 camoufox
305
+ try {
306
+ const cmdProbe = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
307
+ const cmd = String(cmdProbe.stdout || '').toLowerCase();
308
+ if (cmd.includes('camoufox')) {
309
+ console.log(`[camoufox-launch-auth] Stopping known Camoufox process PID ${pid}`);
310
+ try {
311
+ process.kill(pid, 'SIGTERM');
312
+ } catch {
313
+ // 忽略终止失败
314
+ }
315
+ }
316
+ } catch {
317
+ // 忽略 ps 查询失败
318
+ }
319
+ }
320
+ }
267
321
  } catch {
268
- // pkill may not exist; ignore
322
+ // pgrep 可能不存在,忽略
269
323
  }
324
+ console.log('[camoufox-launch-auth] Ensuring Camoufox profile is clean before launch...');
270
325
  const lockNames = ['.parentlock', 'parent.lock', 'lock'];
271
326
  for (const name of lockNames) {
272
327
  const target = path.join(profileDir, name);
@@ -754,7 +809,9 @@ async function runIflowAutoFlow({ url, profileDir, profileId, camoufoxBinary, de
754
809
  );
755
810
  }
756
811
 
757
- const headless = !devMode;
812
+ // iFlow OAuth has poor reliability under headless mode (often returns anti-bot/garbled pages).
813
+ // Keep default headed to match camo CLI behavior; allow opt-in headless via env.
814
+ const headless = resolveIflowHeadlessMode(devMode);
758
815
  const timeoutMs = Number(process.env.ROUTECODEX_CAMOUFOX_IFLOW_TIMEOUT_MS || 300_000);
759
816
  console.log(`[camoufox-launch-auth] Launching Camoufox in ${headless ? 'headless' : 'headed'} mode...`);
760
817
  cleanupExistingCamoufox(profileDir);
@@ -1172,6 +1229,8 @@ async function runQwenAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
1172
1229
  }
1173
1230
 
1174
1231
  const timeoutMs = Number(process.env.ROUTECODEX_CAMOUFOX_QWEN_TIMEOUT_MS || 120_000);
1232
+ const accountPreference = (process.env.ROUTECODEX_CAMOUFOX_ACCOUNT_TEXT || '').trim();
1233
+ const accountWaitMs = Number(process.env.ROUTECODEX_CAMOUFOX_QWEN_ACCOUNT_TIMEOUT_MS || 15_000);
1175
1234
  cleanupExistingCamoufox(profileDir);
1176
1235
  const context = await firefox.launchPersistentContext(profileDir, {
1177
1236
  executablePath: camoufoxBinary,
@@ -1231,6 +1290,54 @@ async function runQwenAutoFlow({ url, profileDir, camoufoxBinary, devMode }) {
1231
1290
  await authPage.waitForLoadState('domcontentloaded', { timeout: pageLoadTimeoutMs }).catch(() => {});
1232
1291
  }
1233
1292
 
1293
+ const qwenAccountSelectors = [
1294
+ 'div.yAlK0b[jsname="bQIQze"]',
1295
+ 'div.pGzURd[jsname="V1ur5d"]',
1296
+ 'span.accountName--ZKlffRBc',
1297
+ 'span[class^="accountName--"]',
1298
+ 'span[class*="accountName--"]',
1299
+ '.account-item',
1300
+ '[class*="account-item"]',
1301
+ '[class*="accountItem"]',
1302
+ '[data-testid*="account"]',
1303
+ '[data-test*="account"]'
1304
+ ];
1305
+ const accountResult = await waitForAnyElementInPages(context, qwenAccountSelectors, accountWaitMs);
1306
+ if (accountResult) {
1307
+ let targetAccount = accountResult.locator.first();
1308
+ if (accountPreference) {
1309
+ const preferred = accountResult.locator.filter({ hasText: accountPreference });
1310
+ if (await preferred.count()) {
1311
+ console.log(`[camoufox-launch-auth] Qwen selecting account matching preference "${accountPreference}"`);
1312
+ targetAccount = preferred.first();
1313
+ } else {
1314
+ console.warn(
1315
+ `[camoufox-launch-auth] Qwen preferred account text "${accountPreference}" not found; falling back to first account`
1316
+ );
1317
+ }
1318
+ }
1319
+ await targetAccount.scrollIntoViewIfNeeded().catch(() => {});
1320
+ await targetAccount.hover({ force: true }).catch(() => {});
1321
+ const handle = await targetAccount.elementHandle();
1322
+ const accountText = await targetAccount.innerText().catch(() => '');
1323
+ if (handle) {
1324
+ console.log(
1325
+ `[camoufox-launch-auth] Qwen account element detected (${accountText || 'unknown label'}), clicking...`
1326
+ );
1327
+ await accountResult.page.evaluate((el) => {
1328
+ const events = ['mouseenter', 'mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'];
1329
+ for (const type of events) {
1330
+ el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
1331
+ }
1332
+ }, handle);
1333
+ } else {
1334
+ await targetAccount.click({ timeout: Math.min(timeoutMs, 10_000) }).catch(() => {});
1335
+ }
1336
+ await accountResult.page.waitForLoadState('domcontentloaded', { timeout: Math.min(timeoutMs, 15_000) }).catch(() => {});
1337
+ } else {
1338
+ console.log('[camoufox-launch-auth] Qwen account selector not detected; proceeding to confirm step...');
1339
+ }
1340
+
1234
1341
  console.log('[camoufox-launch-auth] Qwen authorize page loaded, waiting for confirm button...');
1235
1342
  const confirmSelector = 'button.qwen-confirm-btn';
1236
1343
  const confirmResult = await waitForElementInPages(context, confirmSelector, timeoutMs);
@@ -91,6 +91,7 @@ function checkRootLayout() {
91
91
  'task.md',
92
92
  'tests',
93
93
  'tmp',
94
+ 'tsconfig.jest.json',
94
95
  'tsconfig.json',
95
96
  'vendor',
96
97
  ]);
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ const quiet = process.argv.includes('--quiet');
8
+ const routeCodexHome = path.join(os.homedir(), '.routecodex');
9
+
10
+ function log(message) {
11
+ if (!quiet) {
12
+ console.log(`[cleanup:server-pids] ${message}`);
13
+ }
14
+ }
15
+
16
+ function isPidAlive(pid) {
17
+ if (!Number.isFinite(pid) || pid <= 0) {
18
+ return false;
19
+ }
20
+ try {
21
+ process.kill(pid, 0);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function readProcessCommand(pid) {
29
+ if (process.platform === 'win32') {
30
+ return '';
31
+ }
32
+ try {
33
+ const result = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
34
+ if (result.error || Number(result.status ?? 0) !== 0) {
35
+ return '';
36
+ }
37
+ return String(result.stdout || '').trim().toLowerCase();
38
+ } catch {
39
+ return '';
40
+ }
41
+ }
42
+
43
+ function isTrustedRouteCodexCommand(command) {
44
+ const normalized = String(command || '').trim().toLowerCase();
45
+ if (!normalized) {
46
+ return false;
47
+ }
48
+ if (normalized.includes('routecodex/dist/index.js')) {
49
+ return true;
50
+ }
51
+ if (normalized.includes('routecodex/dist/cli.js')) {
52
+ return true;
53
+ }
54
+ if (normalized.includes('@jsonstudio/rcc') && normalized.includes('/dist/index.js')) {
55
+ return true;
56
+ }
57
+ if (normalized.includes('jsonstudio-rcc') && normalized.includes('/dist/index.js')) {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+
63
+ function isPidListeningOnPort(pid, port) {
64
+ if (process.platform === 'win32') {
65
+ return true;
66
+ }
67
+ try {
68
+ const result = spawnSync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], { encoding: 'utf8' });
69
+ if (result.error) {
70
+ return true;
71
+ }
72
+ if (Number(result.status ?? 0) !== 0) {
73
+ return false;
74
+ }
75
+ const pids = String(result.stdout || '')
76
+ .split(/\r?\n/)
77
+ .map((entry) => Number.parseInt(entry.trim(), 10))
78
+ .filter((entry) => Number.isFinite(entry) && entry > 0);
79
+ return pids.includes(pid);
80
+ } catch {
81
+ return true;
82
+ }
83
+ }
84
+
85
+ function cleanupPidFile(filePath) {
86
+ const fileName = path.basename(filePath);
87
+ const match = fileName.match(/^server-(\d+)\.pid$/i);
88
+ const filePort = match ? Number.parseInt(match[1], 10) : null;
89
+ let raw = '';
90
+ try {
91
+ raw = fs.readFileSync(filePath, 'utf8');
92
+ } catch {
93
+ return { removed: false, reason: 'unreadable' };
94
+ }
95
+ const pid = Number.parseInt(String(raw || '').trim(), 10);
96
+ if (!Number.isFinite(pid) || pid <= 0) {
97
+ fs.rmSync(filePath, { force: true });
98
+ return { removed: true, reason: 'invalid_pid' };
99
+ }
100
+ if (!isPidAlive(pid)) {
101
+ fs.rmSync(filePath, { force: true });
102
+ return { removed: true, reason: 'pid_not_alive' };
103
+ }
104
+
105
+ const command = readProcessCommand(pid);
106
+ if (command && !isTrustedRouteCodexCommand(command)) {
107
+ fs.rmSync(filePath, { force: true });
108
+ return { removed: true, reason: 'pid_not_routecodex' };
109
+ }
110
+ if (Number.isFinite(filePort) && filePort > 0 && !isPidListeningOnPort(pid, filePort)) {
111
+ fs.rmSync(filePath, { force: true });
112
+ return { removed: true, reason: 'pid_not_listening_on_port' };
113
+ }
114
+
115
+ return { removed: false, reason: `kept:${fileName}:${pid}` };
116
+ }
117
+
118
+ function main() {
119
+ if (!fs.existsSync(routeCodexHome)) {
120
+ return;
121
+ }
122
+ const entries = fs.readdirSync(routeCodexHome);
123
+ const candidates = entries
124
+ .filter((name) => /^server-\d+\.pid$/i.test(name) || name === 'server.cli.pid')
125
+ .map((name) => path.join(routeCodexHome, name));
126
+
127
+ let removed = 0;
128
+ let kept = 0;
129
+ for (const filePath of candidates) {
130
+ const result = cleanupPidFile(filePath);
131
+ if (result.removed) {
132
+ removed += 1;
133
+ log(`removed ${path.basename(filePath)} (${result.reason})`);
134
+ } else {
135
+ kept += 1;
136
+ log(`kept ${path.basename(filePath)} (${result.reason})`);
137
+ }
138
+ }
139
+ log(`done removed=${removed} kept=${kept}`);
140
+ }
141
+
142
+ main();
@@ -196,6 +196,12 @@ verify_install() {
196
196
  }
197
197
 
198
198
  verify_server_health() {
199
+ if [ "${ROUTECODEX_INSTALL_SKIP_E2E:-0}" = "1" ]; then
200
+ echo ""
201
+ echo "⏭️ 已跳过全局 CLI 端到端检查(ROUTECODEX_INSTALL_SKIP_E2E=1)"
202
+ return
203
+ fi
204
+
199
205
  local HEALTH_LOG="/tmp/routecodex-install-health-$(date +%s).log"
200
206
  echo ""
201
207
  echo "🩺 执行服务器健康&端到端检查 (chat + anthropic SSE)..."
@@ -244,11 +250,13 @@ cleanup_old_install() {
244
250
  main() {
245
251
  check_node
246
252
  cleanup_old_install
253
+ node scripts/cleanup-stale-server-pids.mjs --quiet || true
247
254
  build_project
248
255
  global_install
249
256
  link_global_llms_dev
250
257
  verify_install
251
258
  verify_server_health
259
+ node scripts/cleanup-stale-server-pids.mjs --quiet || true
252
260
 
253
261
  echo ""
254
262
  echo "🎉 全局安装完成!"
@@ -25,6 +25,7 @@ const state = {
25
25
  logStream: null,
26
26
  logPath: '',
27
27
  baseUrl: '',
28
+ ownsServer: false,
28
29
  };
29
30
  let shuttingDown = false;
30
31
  let responsesSseParser = null;
@@ -791,9 +792,12 @@ async function verifyChatStreaming(baseUrl, model, timeoutMs, samplePayload, cha
791
792
  async function stopServer() {
792
793
  const proc = state.serverProc;
793
794
  const baseUrl = state.baseUrl;
794
- if (baseUrl) {
795
+ if (state.ownsServer && baseUrl) {
795
796
  try { await fetch(`${baseUrl}/shutdown`, { method: 'POST' }).catch(() => {}); } catch { /* ignore */ }
796
797
  }
798
+ if (!state.ownsServer) {
799
+ return;
800
+ }
797
801
  if (!proc) {
798
802
  if (state.logStream) {
799
803
  try { state.logStream.end(); } catch { /* ignore */ }
@@ -903,18 +907,23 @@ async function main() {
903
907
 
904
908
  const model = resolveModel(config);
905
909
  console.log(`🔁 模型: ${model}, 端口: ${port}`);
910
+ const buildRestartOnly = (() => {
911
+ const raw = String(process.env.ROUTECODEX_BUILD_RESTART_ONLY ?? process.env.RCC_BUILD_RESTART_ONLY ?? '').trim().toLowerCase();
912
+ return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
913
+ })();
906
914
 
907
915
  const listeners = detectPortPids(port);
908
- if (listeners.length) {
916
+ const reuseExistingServer = buildRestartOnly && listeners.length > 0;
917
+ if (listeners.length && !reuseExistingServer) {
909
918
  throw new Error(`端口 ${port} 已被使用 (PID: ${listeners.join(', ')}). 请先停止正在运行的 RouteCodex 实例再重试。`);
910
919
  }
920
+ if (reuseExistingServer) {
921
+ console.log(`ℹ build-restart-only: 检测到已运行服务 (PID: ${listeners.join(', ')}),复用现有实例进行验证。`);
922
+ }
911
923
 
912
924
  console.log('🛠️ 动态生成最新的 pipeline 配置...');
913
925
  regeneratePipelineConfig({ port, configPath });
914
926
 
915
- state.logPath = path.join(os.tmpdir(), `routecodex-install-verify-${Date.now()}.log`);
916
- const logStream = fs.createWriteStream(state.logPath, { flags: 'a' });
917
-
918
927
  let command;
919
928
  let commandArgs;
920
929
  const env = { ...process.env };
@@ -933,17 +942,25 @@ async function main() {
933
942
  env.RCC_PORT = String(port);
934
943
  }
935
944
 
936
- console.log(`🚀 启动 RouteCodex server... (launcher=${launcher === 'cli' ? command : 'node dist/index.js'})`);
937
- const serverProc = spawn(command, commandArgs, {
938
- cwd,
939
- env,
940
- stdio: ['ignore', 'pipe', 'pipe'],
941
- detached: false,
942
- });
943
- serverProc.stdout.pipe(logStream, { end: false });
944
- serverProc.stderr.pipe(logStream, { end: false });
945
- state.serverProc = serverProc;
946
- state.logStream = logStream;
945
+ if (!reuseExistingServer) {
946
+ state.logPath = path.join(os.tmpdir(), `routecodex-install-verify-${Date.now()}.log`);
947
+ const logStream = fs.createWriteStream(state.logPath, { flags: 'a' });
948
+ state.ownsServer = true;
949
+ console.log(`🚀 启动 RouteCodex server... (launcher=${launcher === 'cli' ? command : 'node dist/index.js'})`);
950
+ const serverProc = spawn(command, commandArgs, {
951
+ cwd,
952
+ env,
953
+ stdio: ['ignore', 'pipe', 'pipe'],
954
+ detached: false,
955
+ });
956
+ serverProc.stdout.pipe(logStream, { end: false });
957
+ serverProc.stderr.pipe(logStream, { end: false });
958
+ state.serverProc = serverProc;
959
+ state.logStream = logStream;
960
+ } else {
961
+ state.ownsServer = false;
962
+ state.serverProc = null;
963
+ }
947
964
 
948
965
  await waitForHealth(baseUrl, 90000);
949
966
  console.log('✅ server 健康检查通过');