@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
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+
7
+ const ROOT =
8
+ process.env.ROUTECODEX_ERROR_SAMPLES_DIR &&
9
+ process.env.ROUTECODEX_ERROR_SAMPLES_DIR.trim().length
10
+ ? path.resolve(process.env.ROUTECODEX_ERROR_SAMPLES_DIR)
11
+ : path.join(os.homedir(), '.routecodex', 'errorsamples');
12
+
13
+ const SKIP_SUBDIR = (() => {
14
+ const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_SKIP_SUBDIR || '').trim();
15
+ return raw || 'skip';
16
+ })();
17
+ const GOLD_SUBDIR = (() => {
18
+ const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_GOLD_SUBDIR || '').trim();
19
+ return raw || 'gold';
20
+ })();
21
+
22
+ const SHAPE_ERROR_PATTERNS = [
23
+ 'failed to parse function arguments: missing field `cmd`',
24
+ 'failed to parse function arguments: missing field `input`',
25
+ 'failed to parse function arguments: missing field `command`',
26
+ 'invalid type: map, expected a string'
27
+ ];
28
+
29
+ const APPLY_PATCH_SHAPE_PATTERNS = [
30
+ 'invalid patch',
31
+ 'failed to parse',
32
+ 'missing field `input`',
33
+ 'missing field `cmd`',
34
+ 'missing field `command`',
35
+ 'invalid type: map, expected a string'
36
+ ];
37
+
38
+ const APPLY_PATCH_NON_SHAPE_PATTERNS = [
39
+ 'failed to find context',
40
+ 'failed to find expected lines',
41
+ 'failed to read file',
42
+ 'no such file',
43
+ 'file not found'
44
+ ];
45
+
46
+ function isJsonFile(name) {
47
+ return name.toLowerCase().endsWith('.json');
48
+ }
49
+
50
+ async function exists(p) {
51
+ try {
52
+ await fs.access(p);
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ async function collectSampleFiles(rootDir) {
60
+ const files = [];
61
+ const queue = [rootDir];
62
+
63
+ while (queue.length > 0) {
64
+ const currentDir = queue.shift();
65
+ if (!currentDir) continue;
66
+
67
+ let entries = [];
68
+ try {
69
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
70
+ } catch {
71
+ continue;
72
+ }
73
+
74
+ for (const entry of entries) {
75
+ const fullPath = path.join(currentDir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ queue.push(fullPath);
78
+ continue;
79
+ }
80
+ if (!entry.isFile()) continue;
81
+ if (!isJsonFile(entry.name)) continue;
82
+ files.push(fullPath);
83
+ }
84
+ }
85
+
86
+ return files.sort();
87
+ }
88
+
89
+ function classifySample(raw) {
90
+ const lower = raw.toLowerCase();
91
+
92
+ const shapeHits = SHAPE_ERROR_PATTERNS.filter((pattern) => raw.includes(pattern));
93
+ if (shapeHits.length > 0) {
94
+ return { kind: 'shape', hits: shapeHits };
95
+ }
96
+
97
+ if (lower.includes('apply_patch verification failed')) {
98
+ const shapeMatched = APPLY_PATCH_SHAPE_PATTERNS.filter((needle) => lower.includes(needle));
99
+ if (shapeMatched.length > 0) {
100
+ return { kind: 'shape', hits: ['apply_patch verification failed (shape)'] };
101
+ }
102
+ const nonShapeMatched = APPLY_PATCH_NON_SHAPE_PATTERNS.filter((needle) => lower.includes(needle));
103
+ if (nonShapeMatched.length > 0) {
104
+ return { kind: 'non_shape', hits: ['apply_patch verification failed (context/non-shape)'] };
105
+ }
106
+ return { kind: 'non_shape', hits: ['apply_patch verification failed (unknown subtype)'] };
107
+ }
108
+
109
+ return { kind: 'none', hits: [] };
110
+ }
111
+
112
+ function inIgnoredSubdir(filePath) {
113
+ const rel = path.relative(ROOT, filePath);
114
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
115
+ return false;
116
+ }
117
+ const parts = rel.split(path.sep).filter(Boolean);
118
+ return parts.includes(SKIP_SUBDIR) || parts.includes(GOLD_SUBDIR);
119
+ }
120
+
121
+ async function moveIntoSkip(filePath) {
122
+ const rel = path.relative(ROOT, filePath);
123
+ const target = path.join(ROOT, SKIP_SUBDIR, rel);
124
+ await fs.mkdir(path.dirname(target), { recursive: true });
125
+ await fs.rename(filePath, target);
126
+ return target;
127
+ }
128
+
129
+ async function moveIntoGold(filePath) {
130
+ const rel = path.relative(ROOT, filePath);
131
+ const target = path.join(ROOT, GOLD_SUBDIR, rel);
132
+ await fs.mkdir(path.dirname(target), { recursive: true });
133
+ await fs.rename(filePath, target);
134
+ return target;
135
+ }
136
+
137
+ async function main() {
138
+ const apply = process.argv.includes('--apply');
139
+
140
+ if (!(await exists(ROOT))) {
141
+ console.log(`[triage:errorsamples] skip (directory not found: ${ROOT})`);
142
+ return;
143
+ }
144
+
145
+ const files = await collectSampleFiles(ROOT);
146
+ if (!files.length) {
147
+ console.log(`[triage:errorsamples] skip (no *.json samples under ${ROOT})`);
148
+ return;
149
+ }
150
+
151
+ const shape = [];
152
+ const nonShape = [];
153
+ for (const file of files) {
154
+ if (inIgnoredSubdir(file)) {
155
+ continue;
156
+ }
157
+ let raw = '';
158
+ try {
159
+ raw = await fs.readFile(file, 'utf-8');
160
+ } catch {
161
+ continue;
162
+ }
163
+ const classified = classifySample(raw);
164
+ if (classified.kind === 'shape') {
165
+ shape.push({ file, hits: classified.hits });
166
+ } else if (classified.kind === 'non_shape') {
167
+ nonShape.push({ file, hits: classified.hits });
168
+ }
169
+ }
170
+
171
+ console.log(`[triage:errorsamples] scanned: ${files.length}`);
172
+ console.log(`[triage:errorsamples] shape/actionable: ${shape.length}`);
173
+ console.log(`[triage:errorsamples] non-shape/skip-candidate: ${nonShape.length}`);
174
+
175
+ if (shape.length > 0) {
176
+ console.log('[triage:errorsamples] actionable shape samples (move to gold regression set):');
177
+ for (const item of shape.slice(0, 20)) {
178
+ console.log(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
179
+ }
180
+ if (shape.length > 20) {
181
+ console.log(` - ... and ${shape.length - 20} more`);
182
+ }
183
+ }
184
+
185
+ if (!apply) {
186
+ console.log(`[triage:errorsamples] dry-run complete (pass --apply to move shape -> ${GOLD_SUBDIR}/ and non-shape -> ${SKIP_SUBDIR}/)`);
187
+ return;
188
+ }
189
+
190
+ let movedGold = 0;
191
+ for (const item of shape) {
192
+ try {
193
+ await moveIntoGold(item.file);
194
+ movedGold += 1;
195
+ } catch {
196
+ // best-effort
197
+ }
198
+ }
199
+
200
+ let movedSkip = 0;
201
+ for (const item of nonShape) {
202
+ try {
203
+ await moveIntoSkip(item.file);
204
+ movedSkip += 1;
205
+ } catch {
206
+ // best-effort
207
+ }
208
+ }
209
+ console.log(`[triage:errorsamples] moved to ${GOLD_SUBDIR}/: ${movedGold}/${shape.length}`);
210
+ console.log(`[triage:errorsamples] moved to ${SKIP_SUBDIR}/: ${movedSkip}/${nonShape.length}`);
211
+ }
212
+
213
+ main().catch((error) => {
214
+ console.error('[triage:errorsamples] failed:', error);
215
+ process.exit(99);
216
+ });
@@ -22,13 +22,40 @@ const ROOT =
22
22
  process.env.ROUTECODEX_ERROR_SAMPLES_DIR.trim().length
23
23
  ? path.resolve(process.env.ROUTECODEX_ERROR_SAMPLES_DIR)
24
24
  : path.join(os.homedir(), '.routecodex', 'errorsamples');
25
-
26
- const ERROR_PATTERNS = [
27
- // exec_command / apply_patch 参数解码错误(CLI 侧报错)
25
+ const SKIP_SUBDIR = (() => {
26
+ const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_SKIP_SUBDIR || '').trim();
27
+ return raw || 'skip';
28
+ })();
29
+ const GOLD_SUBDIR = (() => {
30
+ const raw = String(process.env.ROUTECODEX_ERROR_SAMPLES_GOLD_SUBDIR || '').trim();
31
+ return raw || 'gold';
32
+ })();
33
+
34
+ const SHAPE_ERROR_PATTERNS = [
35
+ // exec_command / shell_command / apply_patch 参数形状错误
28
36
  'failed to parse function arguments: missing field `cmd`',
29
37
  'failed to parse function arguments: missing field `input`',
30
- // 历史回滚:统一 diff 校验失败
31
- 'apply_patch verification failed'
38
+ 'failed to parse function arguments: missing field `command`',
39
+ 'invalid type: map, expected a string'
40
+ ];
41
+
42
+ const APPLY_PATCH_SHAPE_PATTERNS = [
43
+ // apply_patch 仍属于“可修复形状问题”的子类
44
+ 'invalid patch',
45
+ 'failed to parse',
46
+ 'missing field `input`',
47
+ 'missing field `cmd`',
48
+ 'missing field `command`',
49
+ 'invalid type: map, expected a string'
50
+ ];
51
+
52
+ const APPLY_PATCH_NON_SHAPE_PATTERNS = [
53
+ // 这些是上下文/执行问题,不应作为“形状回归”阻塞构建
54
+ 'failed to find context',
55
+ 'failed to find expected lines',
56
+ 'failed to read file',
57
+ 'no such file',
58
+ 'file not found'
32
59
  ];
33
60
 
34
61
  async function fileExists(p) {
@@ -43,6 +70,8 @@ async function fileExists(p) {
43
70
  async function collectSampleFiles(rootDir) {
44
71
  const files = [];
45
72
  const queue = [rootDir];
73
+ const skippedDirs = [];
74
+ const goldDirs = [];
46
75
 
47
76
  while (queue.length > 0) {
48
77
  const currentDir = queue.shift();
@@ -60,6 +89,14 @@ async function collectSampleFiles(rootDir) {
60
89
  for (const entry of entries) {
61
90
  const fullPath = path.join(currentDir, entry.name);
62
91
  if (entry.isDirectory()) {
92
+ if (entry.name === SKIP_SUBDIR) {
93
+ skippedDirs.push(fullPath);
94
+ continue;
95
+ }
96
+ if (entry.name === GOLD_SUBDIR) {
97
+ goldDirs.push(fullPath);
98
+ continue;
99
+ }
63
100
  queue.push(fullPath);
64
101
  continue;
65
102
  }
@@ -69,18 +106,38 @@ async function collectSampleFiles(rootDir) {
69
106
  }
70
107
  }
71
108
 
72
- return files.sort();
109
+ return {
110
+ files: files.sort(),
111
+ skippedDirs,
112
+ goldDirs
113
+ };
73
114
  }
74
115
 
75
116
  async function checkFile(filePath) {
76
117
  const raw = await fs.readFile(filePath, 'utf-8');
77
- const hits = [];
78
- for (const pattern of ERROR_PATTERNS) {
118
+ const lower = raw.toLowerCase();
119
+ const failHits = [];
120
+ const warnHits = [];
121
+
122
+ for (const pattern of SHAPE_ERROR_PATTERNS) {
79
123
  if (raw.includes(pattern)) {
80
- hits.push(pattern);
124
+ failHits.push(pattern);
125
+ }
126
+ }
127
+
128
+ if (lower.includes('apply_patch verification failed')) {
129
+ const shapeMatched = APPLY_PATCH_SHAPE_PATTERNS.some((needle) => lower.includes(needle));
130
+ const nonShapeMatched = APPLY_PATCH_NON_SHAPE_PATTERNS.some((needle) => lower.includes(needle));
131
+ if (shapeMatched) {
132
+ failHits.push('apply_patch verification failed (shape)');
133
+ } else if (nonShapeMatched) {
134
+ warnHits.push('apply_patch verification failed (context/non-shape)');
135
+ } else {
136
+ warnHits.push('apply_patch verification failed (unknown subtype)');
81
137
  }
82
138
  }
83
- return hits;
139
+
140
+ return { failHits, warnHits };
84
141
  }
85
142
 
86
143
  async function main() {
@@ -89,24 +146,44 @@ async function main() {
89
146
  return;
90
147
  }
91
148
 
92
- const files = await collectSampleFiles(ROOT);
149
+ const { files, skippedDirs, goldDirs } = await collectSampleFiles(ROOT);
93
150
  if (!files.length) {
94
151
  console.log(`[verify:errorsamples] skip (no *.json samples under ${ROOT})`);
95
152
  return;
96
153
  }
97
154
 
98
155
  console.log(`[verify:errorsamples] scanning ${files.length} sample(s) under ${ROOT}`);
156
+ if (skippedDirs.length > 0) {
157
+ console.log(`[verify:errorsamples] skip subdir "${SKIP_SUBDIR}" (${skippedDirs.length} dir(s))`);
158
+ }
159
+ if (goldDirs.length > 0) {
160
+ console.log(`[verify:errorsamples] gold subdir "${GOLD_SUBDIR}" (${goldDirs.length} dir(s))`);
161
+ }
99
162
 
100
163
  const failures = [];
164
+ const warnings = [];
101
165
  for (const file of files) {
102
- const hits = await checkFile(file);
103
- if (hits.length) {
104
- failures.push({ file, hits });
166
+ const { failHits, warnHits } = await checkFile(file);
167
+ if (failHits.length) {
168
+ failures.push({ file, hits: failHits });
169
+ }
170
+ if (warnHits.length) {
171
+ warnings.push({ file, hits: warnHits });
172
+ }
173
+ }
174
+
175
+ if (warnings.length > 0) {
176
+ console.log(`[verify:errorsamples] warning-only samples: ${warnings.length}`);
177
+ for (const item of warnings.slice(0, 8)) {
178
+ console.log(` - ${path.basename(item.file)} → ${item.hits.join(', ')}`);
179
+ }
180
+ if (warnings.length > 8) {
181
+ console.log(` - ... and ${warnings.length - 8} more warning sample(s)`);
105
182
  }
106
183
  }
107
184
 
108
185
  if (!failures.length) {
109
- console.log('[verify:errorsamples] ✅ no known error patterns found');
186
+ console.log('[verify:errorsamples] ✅ no shape-error regressions found');
110
187
  return;
111
188
  }
112
189
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
2
+ import { spawn, spawnSync } from 'node:child_process';
3
3
  import process from 'node:process';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
@@ -33,6 +33,16 @@ const AGENTS_INSTRUCTIONS = (() => {
33
33
  }
34
34
  })();
35
35
 
36
+ function cleanupStaleServerPidFiles() {
37
+ try {
38
+ spawnSync('node', [path.join(__dirname, 'cleanup-stale-server-pids.mjs'), '--quiet'], {
39
+ stdio: 'ignore'
40
+ });
41
+ } catch {
42
+ // ignore cleanup failures
43
+ }
44
+ }
45
+
36
46
  function readServerApiKeyFromConfig(configPath) {
37
47
  try {
38
48
  const raw = fs.readFileSync(configPath, 'utf8');
@@ -102,6 +112,7 @@ async function main() {
102
112
  await runGeminiCliStartupCheck();
103
113
  } finally {
104
114
  shutdown();
115
+ cleanupStaleServerPidFiles();
105
116
  if (resolved.tempDir) {
106
117
  try {
107
118
  fs.rmSync(resolved.tempDir, { recursive: true, force: true });
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
2
+ import { spawn, spawnSync } from 'node:child_process';
3
3
  import { once } from 'node:events';
4
4
  import { setTimeout as sleep } from 'node:timers/promises';
5
5
  import http from 'node:http';
@@ -7,26 +7,37 @@ import fs from 'node:fs';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
9
 
10
- const PORT = Number(process.env.ROUTECODEX_INSTALL_VERIFY_PORT || process.env.RCC_INSTALL_VERIFY_PORT || 5560);
10
+ const REQUESTED_PORT = Number(process.env.ROUTECODEX_INSTALL_VERIFY_PORT || process.env.RCC_INSTALL_VERIFY_PORT || 5560);
11
11
  const HOST = process.env.ROUTECODEX_INSTALL_VERIFY_HOST || '127.0.0.1';
12
- const BASE_URL = `http://${HOST}:${PORT}`;
12
+ const PORT_SEARCH_LIMIT = 50;
13
13
 
14
- function resolveRoutecodexBinary() {
15
- const prefix = process.env.npm_config_prefix || process.env.PREFIX;
16
- if (prefix) {
17
- const candidate = path.join(prefix, 'bin', process.platform === 'win32' ? 'routecodex.cmd' : 'routecodex');
18
- if (fs.existsSync(candidate)) {
19
- return candidate;
20
- }
14
+ function resolveServerEntryArgs() {
15
+ const serverEntry = path.join(process.cwd(), 'dist', 'index.js');
16
+ const modulesConfigPath = path.join(process.cwd(), 'dist', 'config', 'modules.json');
17
+ if (!fs.existsSync(serverEntry)) {
18
+ throw new Error(`verify-install-e2e missing server entry: ${serverEntry}`);
19
+ }
20
+ if (!fs.existsSync(modulesConfigPath)) {
21
+ throw new Error(`verify-install-e2e missing modules config: ${modulesConfigPath}`);
21
22
  }
22
- return 'routecodex';
23
+ return [serverEntry, modulesConfigPath];
23
24
  }
24
25
 
25
- async function waitForHealth(timeoutMs = 60000) {
26
+ function cleanupStaleServerPidFiles() {
27
+ try {
28
+ spawnSync('node', [path.join(process.cwd(), 'scripts', 'cleanup-stale-server-pids.mjs'), '--quiet'], {
29
+ stdio: 'ignore'
30
+ });
31
+ } catch {
32
+ // ignore cleanup failures
33
+ }
34
+ }
35
+
36
+ async function waitForHealth(baseUrl, timeoutMs = 60000) {
26
37
  const start = Date.now();
27
38
  while (Date.now() - start < timeoutMs) {
28
39
  try {
29
- const res = await fetch(`${BASE_URL}/health`);
40
+ const res = await fetch(`${baseUrl}/health`);
30
41
  if (res.ok) {
31
42
  return;
32
43
  }
@@ -38,7 +49,7 @@ async function waitForHealth(timeoutMs = 60000) {
38
49
  throw new Error('Timed out waiting for /health');
39
50
  }
40
51
 
41
- async function runChatTest() {
52
+ async function runChatTest(baseUrl) {
42
53
  const payload = {
43
54
  model: process.env.ROUTECODEX_VERIFY_CHAT_MODEL || 'glm-4.6',
44
55
  messages: [
@@ -47,7 +58,7 @@ async function runChatTest() {
47
58
  ],
48
59
  stream: false
49
60
  };
50
- const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
61
+ const res = await fetch(`${baseUrl}/v1/chat/completions`, {
51
62
  method: 'POST',
52
63
  headers: { 'Content-Type': 'application/json' },
53
64
  body: JSON.stringify(payload)
@@ -66,7 +77,7 @@ async function runChatTest() {
66
77
  }
67
78
  }
68
79
 
69
- async function runAnthropicSseTest() {
80
+ async function runAnthropicSseTest(baseUrl) {
70
81
  const payload = {
71
82
  model: process.env.ROUTECODEX_VERIFY_ANTHROPIC_MODEL || 'glm-4.6',
72
83
  messages: [
@@ -74,7 +85,7 @@ async function runAnthropicSseTest() {
74
85
  ],
75
86
  stream: true
76
87
  };
77
- const res = await fetch(`${BASE_URL}/v1/messages`, {
88
+ const res = await fetch(`${baseUrl}/v1/messages`, {
78
89
  method: 'POST',
79
90
  headers: {
80
91
  'Content-Type': 'application/json',
@@ -115,14 +126,16 @@ async function runAnthropicSseTest() {
115
126
  }
116
127
 
117
128
  async function main() {
118
- const routecodexBin = resolveRoutecodexBinary();
129
+ const port = await resolveVerifyPort(REQUESTED_PORT, HOST);
130
+ const baseUrl = `http://${HOST}:${port}`;
131
+ const serverArgs = resolveServerEntryArgs();
119
132
  const customConfigPath = process.env.ROUTECODEX_INSTALL_CONFIG;
120
133
  const mockServer = customConfigPath ? null : await startMockProviderServer();
121
- const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl);
134
+ const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl, HOST, port);
122
135
  const env = {
123
136
  ...process.env,
124
- ROUTECODEX_PORT: String(PORT),
125
- RCC_PORT: String(PORT),
137
+ ROUTECODEX_PORT: String(port),
138
+ RCC_PORT: String(port),
126
139
  ROUTECODEX_HOST: HOST,
127
140
  RCC_HOST: HOST,
128
141
  ROUTECODEX_CONFIG: verifyConfigPath,
@@ -132,7 +145,7 @@ async function main() {
132
145
  // does not depend on external network availability.
133
146
  ROUTECODEX_USE_MOCK: '1'
134
147
  };
135
- const server = spawn(routecodexBin, ['start'], {
148
+ const server = spawn(process.execPath, serverArgs, {
136
149
  env,
137
150
  stdio: ['ignore', 'pipe', 'pipe']
138
151
  });
@@ -154,9 +167,9 @@ async function main() {
154
167
  });
155
168
 
156
169
  try {
157
- await waitForHealth();
158
- await runChatTest();
159
- await runAnthropicSseTest();
170
+ await waitForHealth(baseUrl);
171
+ await runChatTest(baseUrl);
172
+ await runAnthropicSseTest(baseUrl);
160
173
  } finally {
161
174
  if (!serverExited) {
162
175
  server.kill('SIGTERM');
@@ -165,6 +178,7 @@ async function main() {
165
178
  if (mockServer) {
166
179
  await mockServer.close();
167
180
  }
181
+ cleanupStaleServerPidFiles();
168
182
  }
169
183
  }
170
184
 
@@ -284,14 +298,14 @@ function extractUserPrompt(payload) {
284
298
  : 'verification prompt';
285
299
  }
286
300
 
287
- async function writeVerifyConfig(baseUrl) {
301
+ async function writeVerifyConfig(baseUrl, host, port) {
288
302
  const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'routecodex-verify-'));
289
303
  const filePath = path.join(dir, 'config.json');
290
304
  const config = {
291
305
  version: '1.0.0',
292
306
  httpserver: {
293
- host: HOST,
294
- port: PORT
307
+ host,
308
+ port
295
309
  },
296
310
  virtualrouter: {
297
311
  inputProtocol: 'openai',
@@ -324,3 +338,30 @@ async function writeVerifyConfig(baseUrl) {
324
338
  await fs.promises.writeFile(filePath, JSON.stringify(config, null, 2), 'utf8');
325
339
  return filePath;
326
340
  }
341
+
342
+ async function resolveVerifyPort(preferredPort, host) {
343
+ const basePort = Number.isFinite(preferredPort) && preferredPort > 0 ? preferredPort : 5560;
344
+ for (let offset = 0; offset < PORT_SEARCH_LIMIT; offset += 1) {
345
+ const port = basePort + offset;
346
+ // eslint-disable-next-line no-await-in-loop
347
+ if (await canListenOnPort(port, host)) {
348
+ return port;
349
+ }
350
+ }
351
+ throw new Error(`No available verification port in range ${basePort}-${basePort + PORT_SEARCH_LIMIT - 1}`);
352
+ }
353
+
354
+ async function canListenOnPort(port, host) {
355
+ const probe = http.createServer();
356
+ try {
357
+ await new Promise((resolve, reject) => {
358
+ probe.once('error', reject);
359
+ probe.listen(port, host, resolve);
360
+ });
361
+ return true;
362
+ } catch {
363
+ return false;
364
+ } finally {
365
+ await new Promise((resolve) => probe.close(() => resolve(undefined))).catch(() => {});
366
+ }
367
+ }