@jsonstudio/rcc 0.89.2239 → 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 (237) 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/launcher/types.d.ts +6 -0
  14. package/dist/cli/commands/launcher-kernel.js +456 -109
  15. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  16. package/dist/cli/commands/port.js +28 -8
  17. package/dist/cli/commands/port.js.map +1 -1
  18. package/dist/cli/commands/restart.d.ts +4 -0
  19. package/dist/cli/commands/restart.js +91 -42
  20. package/dist/cli/commands/restart.js.map +1 -1
  21. package/dist/cli/commands/start-types.d.ts +4 -0
  22. package/dist/cli/commands/start.js +108 -65
  23. package/dist/cli/commands/start.js.map +1 -1
  24. package/dist/cli/commands/stop.d.ts +3 -0
  25. package/dist/cli/commands/stop.js +30 -63
  26. package/dist/cli/commands/stop.js.map +1 -1
  27. package/dist/cli/config/init-provider-catalog.js +8 -3
  28. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  29. package/dist/cli/guardian/client.d.ts +38 -0
  30. package/dist/cli/guardian/client.js +237 -0
  31. package/dist/cli/guardian/client.js.map +1 -0
  32. package/dist/cli/guardian/paths.d.ts +7 -0
  33. package/dist/cli/guardian/paths.js +13 -0
  34. package/dist/cli/guardian/paths.js.map +1 -0
  35. package/dist/cli/guardian/types.d.ts +30 -0
  36. package/dist/cli/guardian/types.js +2 -0
  37. package/dist/cli/guardian/types.js.map +1 -0
  38. package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
  39. package/dist/cli/register/guardian-daemon-command.js +5 -0
  40. package/dist/cli/register/guardian-daemon-command.js.map +1 -0
  41. package/dist/cli/server/port-utils.js +57 -1
  42. package/dist/cli/server/port-utils.js.map +1 -1
  43. package/dist/cli.js +48 -0
  44. package/dist/cli.js.map +1 -1
  45. package/dist/commands/oauth.js +6 -6
  46. package/dist/commands/oauth.js.map +1 -1
  47. package/dist/config/routecodex-config-loader.js +66 -1
  48. package/dist/config/routecodex-config-loader.js.map +1 -1
  49. package/dist/config/virtual-router-builder.js +18 -0
  50. package/dist/config/virtual-router-builder.js.map +1 -1
  51. package/dist/config/virtual-router-types.js +20 -5
  52. package/dist/config/virtual-router-types.js.map +1 -1
  53. package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
  54. package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
  55. package/dist/daemon-admin-ui/index.html +13 -0
  56. package/dist/docs/daemon-admin-ui.html +328 -57
  57. package/dist/index.d.ts +9 -0
  58. package/dist/index.js +268 -10
  59. package/dist/index.js.map +1 -1
  60. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
  61. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
  62. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
  63. package/dist/manager/modules/quota/provider-quota-daemon.events.js +50 -1
  64. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  65. package/dist/providers/auth/antigravity-user-agent.js +78 -31
  66. package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
  67. package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
  68. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  69. package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
  70. package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
  71. package/dist/providers/auth/oauth-error-message.d.ts +1 -0
  72. package/dist/providers/auth/oauth-error-message.js +44 -0
  73. package/dist/providers/auth/oauth-error-message.js.map +1 -0
  74. package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
  75. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
  76. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
  77. package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
  78. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
  79. package/dist/providers/auth/oauth-lifecycle.js +502 -87
  80. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  81. package/dist/providers/auth/oauth-repair-cooldown.js +2 -7
  82. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  83. package/dist/providers/auth/oauth-repair-env.js +3 -5
  84. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  85. package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
  86. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
  87. package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
  88. package/dist/providers/core/config/camoufox-actions.js +461 -0
  89. package/dist/providers/core/config/camoufox-actions.js.map +1 -0
  90. package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
  91. package/dist/providers/core/config/camoufox-launcher.js +518 -160
  92. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  93. package/dist/providers/core/config/oauth-flows.js +6 -44
  94. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  95. package/dist/providers/core/config/provider-oauth-configs.js +51 -7
  96. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  97. package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
  98. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  99. package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
  100. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
  101. package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
  102. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
  103. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  104. package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
  105. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  106. package/dist/providers/core/strategies/oauth-device-flow.js +6 -3
  107. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  108. package/dist/providers/profile/families/iflow-profile.js +83 -10
  109. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  110. package/dist/scripts/camoufox/launch-auth.mjs +112 -5
  111. package/dist/server/handlers/config-admin-handler.js +9 -2
  112. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  113. package/dist/server/handlers/handler-utils.js +3 -12
  114. package/dist/server/handlers/handler-utils.js.map +1 -1
  115. package/dist/server/handlers/logging.js +3 -4
  116. package/dist/server/handlers/logging.js.map +1 -1
  117. package/dist/server/runtime/http-server/clock-client-reaper.js +3 -26
  118. package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -1
  119. package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +4 -0
  120. package/dist/server/runtime/http-server/clock-client-registry-utils.js +74 -16
  121. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -1
  122. package/dist/server/runtime/http-server/clock-client-registry.d.ts +15 -0
  123. package/dist/server/runtime/http-server/clock-client-registry.js +300 -6
  124. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
  125. package/dist/server/runtime/http-server/clock-client-routes.js +49 -19
  126. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
  127. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +16 -0
  128. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +49 -0
  129. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -1
  130. package/dist/server/runtime/http-server/clock-scope-resolution.d.ts +14 -0
  131. package/dist/server/runtime/http-server/clock-scope-resolution.js +212 -0
  132. package/dist/server/runtime/http-server/clock-scope-resolution.js.map +1 -0
  133. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
  134. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  135. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
  136. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  137. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
  138. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  139. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
  140. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
  141. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  142. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
  143. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  144. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +18 -29
  145. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  146. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
  147. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  148. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
  149. package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
  150. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  151. package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
  152. package/dist/server/runtime/http-server/executor/client-injection-flow.js +287 -0
  153. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
  154. package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
  155. package/dist/server/runtime/http-server/executor/index.js +1 -1
  156. package/dist/server/runtime/http-server/executor/index.js.map +1 -1
  157. package/dist/server/runtime/http-server/executor/provider-response-converter.js +236 -62
  158. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  159. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +1 -0
  160. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +12 -0
  161. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
  162. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +16 -12
  163. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  164. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
  165. package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
  166. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
  167. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
  168. package/dist/server/runtime/http-server/executor/usage-aggregator.js +84 -88
  169. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  170. package/dist/server/runtime/http-server/executor-metadata.js +328 -7
  171. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  172. package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
  173. package/dist/server/runtime/http-server/executor-response.js +52 -58
  174. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  175. package/dist/server/runtime/http-server/http-server-bootstrap.js +50 -6
  176. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  177. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +1 -0
  178. package/dist/server/runtime/http-server/http-server-clock-daemon.js +186 -44
  179. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -1
  180. package/dist/server/runtime/http-server/http-server-lifecycle.js +4 -4
  181. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  182. package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
  183. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  184. package/dist/server/runtime/http-server/index.d.ts +1 -0
  185. package/dist/server/runtime/http-server/index.js +1 -0
  186. package/dist/server/runtime/http-server/index.js.map +1 -1
  187. package/dist/server/runtime/http-server/middleware.js +82 -4
  188. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  189. package/dist/server/runtime/http-server/request-executor.js +6 -5
  190. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  191. package/dist/server/runtime/http-server/routes.d.ts +2 -1
  192. package/dist/server/runtime/http-server/routes.js +4 -2
  193. package/dist/server/runtime/http-server/routes.js.map +1 -1
  194. package/dist/server/runtime/http-server/session-dir.js +12 -1
  195. package/dist/server/runtime/http-server/session-dir.js.map +1 -1
  196. package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
  197. package/dist/server/runtime/http-server/stats-manager.js +269 -21
  198. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  199. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +13 -0
  200. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +168 -0
  201. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
  202. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
  203. package/dist/server/runtime/http-server/tmux-session-probe.js +97 -0
  204. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  205. package/dist/server-lifecycle/port-utils.d.ts +2 -1
  206. package/dist/server-lifecycle/port-utils.js +84 -4
  207. package/dist/server-lifecycle/port-utils.js.map +1 -1
  208. package/dist/token-daemon/index.d.ts +1 -0
  209. package/dist/token-daemon/index.js +17 -12
  210. package/dist/token-daemon/index.js.map +1 -1
  211. package/dist/utils/clock-client-token.d.ts +2 -1
  212. package/dist/utils/clock-client-token.js +52 -8
  213. package/dist/utils/clock-client-token.js.map +1 -1
  214. package/dist/utils/clock-scope-trace.d.ts +11 -0
  215. package/dist/utils/clock-scope-trace.js +41 -0
  216. package/dist/utils/clock-scope-trace.js.map +1 -0
  217. package/dist/utils/llms-engine-shadow.js +1 -1
  218. package/dist/utils/llms-engine-shadow.js.map +1 -1
  219. package/docs/DAEMON_CONTROL_PLANE.md +1 -0
  220. package/docs/ROUTING_POLICY_SCHEMA.md +4 -2
  221. package/docs/daemon-admin-ui.html +328 -57
  222. package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
  223. package/docs/exec-command-guard-policy.example.v1.json +7 -1
  224. package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
  225. package/package.json +21 -5
  226. package/scripts/build-core.mjs +12 -0
  227. package/scripts/camoufox/launch-auth.mjs +112 -5
  228. package/scripts/ci/repo-sanity.mjs +1 -0
  229. package/scripts/install-global.sh +6 -0
  230. package/scripts/install-verify.mjs +33 -16
  231. package/scripts/run-bg.sh +226 -43
  232. package/scripts/run-fg-gtimeout.sh +158 -14
  233. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
  234. package/scripts/tests/ci-jest.mjs +9 -1
  235. package/scripts/triage-errorsamples.mjs +216 -0
  236. package/scripts/verify-codex-error-samples.mjs +92 -15
  237. package/scripts/verify-install-e2e.mjs +57 -27
@@ -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
 
@@ -7,19 +7,20 @@ 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
26
  function cleanupStaleServerPidFiles() {
@@ -32,11 +33,11 @@ function cleanupStaleServerPidFiles() {
32
33
  }
33
34
  }
34
35
 
35
- async function waitForHealth(timeoutMs = 60000) {
36
+ async function waitForHealth(baseUrl, timeoutMs = 60000) {
36
37
  const start = Date.now();
37
38
  while (Date.now() - start < timeoutMs) {
38
39
  try {
39
- const res = await fetch(`${BASE_URL}/health`);
40
+ const res = await fetch(`${baseUrl}/health`);
40
41
  if (res.ok) {
41
42
  return;
42
43
  }
@@ -48,7 +49,7 @@ async function waitForHealth(timeoutMs = 60000) {
48
49
  throw new Error('Timed out waiting for /health');
49
50
  }
50
51
 
51
- async function runChatTest() {
52
+ async function runChatTest(baseUrl) {
52
53
  const payload = {
53
54
  model: process.env.ROUTECODEX_VERIFY_CHAT_MODEL || 'glm-4.6',
54
55
  messages: [
@@ -57,7 +58,7 @@ async function runChatTest() {
57
58
  ],
58
59
  stream: false
59
60
  };
60
- const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
61
+ const res = await fetch(`${baseUrl}/v1/chat/completions`, {
61
62
  method: 'POST',
62
63
  headers: { 'Content-Type': 'application/json' },
63
64
  body: JSON.stringify(payload)
@@ -76,7 +77,7 @@ async function runChatTest() {
76
77
  }
77
78
  }
78
79
 
79
- async function runAnthropicSseTest() {
80
+ async function runAnthropicSseTest(baseUrl) {
80
81
  const payload = {
81
82
  model: process.env.ROUTECODEX_VERIFY_ANTHROPIC_MODEL || 'glm-4.6',
82
83
  messages: [
@@ -84,7 +85,7 @@ async function runAnthropicSseTest() {
84
85
  ],
85
86
  stream: true
86
87
  };
87
- const res = await fetch(`${BASE_URL}/v1/messages`, {
88
+ const res = await fetch(`${baseUrl}/v1/messages`, {
88
89
  method: 'POST',
89
90
  headers: {
90
91
  'Content-Type': 'application/json',
@@ -125,14 +126,16 @@ async function runAnthropicSseTest() {
125
126
  }
126
127
 
127
128
  async function main() {
128
- const routecodexBin = resolveRoutecodexBinary();
129
+ const port = await resolveVerifyPort(REQUESTED_PORT, HOST);
130
+ const baseUrl = `http://${HOST}:${port}`;
131
+ const serverArgs = resolveServerEntryArgs();
129
132
  const customConfigPath = process.env.ROUTECODEX_INSTALL_CONFIG;
130
133
  const mockServer = customConfigPath ? null : await startMockProviderServer();
131
- const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl);
134
+ const verifyConfigPath = customConfigPath || await writeVerifyConfig(mockServer.baseUrl, HOST, port);
132
135
  const env = {
133
136
  ...process.env,
134
- ROUTECODEX_PORT: String(PORT),
135
- RCC_PORT: String(PORT),
137
+ ROUTECODEX_PORT: String(port),
138
+ RCC_PORT: String(port),
136
139
  ROUTECODEX_HOST: HOST,
137
140
  RCC_HOST: HOST,
138
141
  ROUTECODEX_CONFIG: verifyConfigPath,
@@ -142,7 +145,7 @@ async function main() {
142
145
  // does not depend on external network availability.
143
146
  ROUTECODEX_USE_MOCK: '1'
144
147
  };
145
- const server = spawn(routecodexBin, ['start'], {
148
+ const server = spawn(process.execPath, serverArgs, {
146
149
  env,
147
150
  stdio: ['ignore', 'pipe', 'pipe']
148
151
  });
@@ -164,9 +167,9 @@ async function main() {
164
167
  });
165
168
 
166
169
  try {
167
- await waitForHealth();
168
- await runChatTest();
169
- await runAnthropicSseTest();
170
+ await waitForHealth(baseUrl);
171
+ await runChatTest(baseUrl);
172
+ await runAnthropicSseTest(baseUrl);
170
173
  } finally {
171
174
  if (!serverExited) {
172
175
  server.kill('SIGTERM');
@@ -295,14 +298,14 @@ function extractUserPrompt(payload) {
295
298
  : 'verification prompt';
296
299
  }
297
300
 
298
- async function writeVerifyConfig(baseUrl) {
301
+ async function writeVerifyConfig(baseUrl, host, port) {
299
302
  const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'routecodex-verify-'));
300
303
  const filePath = path.join(dir, 'config.json');
301
304
  const config = {
302
305
  version: '1.0.0',
303
306
  httpserver: {
304
- host: HOST,
305
- port: PORT
307
+ host,
308
+ port
306
309
  },
307
310
  virtualrouter: {
308
311
  inputProtocol: 'openai',
@@ -335,3 +338,30 @@ async function writeVerifyConfig(baseUrl) {
335
338
  await fs.promises.writeFile(filePath, JSON.stringify(config, null, 2), 'utf8');
336
339
  return filePath;
337
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
+ }