@jsonstudio/rcc 0.89.1121 → 0.89.1189

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 (194) hide show
  1. package/dist/build-info.js +2 -2
  2. package/dist/cli/commands/clean.d.ts +16 -0
  3. package/dist/cli/commands/clean.js +58 -0
  4. package/dist/cli/commands/clean.js.map +1 -0
  5. package/dist/cli/commands/code.d.ts +55 -0
  6. package/dist/cli/commands/code.js +376 -0
  7. package/dist/cli/commands/code.js.map +1 -0
  8. package/dist/cli/commands/config.d.ts +31 -0
  9. package/dist/cli/commands/config.js +168 -0
  10. package/dist/cli/commands/config.js.map +1 -0
  11. package/dist/cli/commands/env.d.ts +20 -0
  12. package/dist/cli/commands/env.js +73 -0
  13. package/dist/cli/commands/env.js.map +1 -0
  14. package/dist/cli/commands/examples.d.ts +5 -0
  15. package/dist/cli/commands/examples.js +66 -0
  16. package/dist/cli/commands/examples.js.map +1 -0
  17. package/dist/cli/commands/port.d.ts +24 -0
  18. package/dist/cli/commands/port.js +85 -0
  19. package/dist/cli/commands/port.js.map +1 -0
  20. package/dist/cli/commands/restart.d.ts +50 -0
  21. package/dist/cli/commands/restart.js +176 -0
  22. package/dist/cli/commands/restart.js.map +1 -0
  23. package/dist/cli/commands/start.d.ts +68 -0
  24. package/dist/cli/commands/start.js +295 -0
  25. package/dist/cli/commands/start.js.map +1 -0
  26. package/dist/cli/commands/status.d.ts +16 -0
  27. package/dist/cli/commands/status.js +104 -0
  28. package/dist/cli/commands/status.js.map +1 -0
  29. package/dist/cli/commands/stop.d.ts +35 -0
  30. package/dist/cli/commands/stop.js +95 -0
  31. package/dist/cli/commands/stop.js.map +1 -0
  32. package/dist/cli/logger.d.ts +8 -0
  33. package/dist/cli/logger.js +9 -0
  34. package/dist/cli/logger.js.map +1 -0
  35. package/dist/cli/main.d.ts +6 -0
  36. package/dist/cli/main.js +16 -0
  37. package/dist/cli/main.js.map +1 -0
  38. package/dist/cli/program.d.ts +8 -0
  39. package/dist/cli/program.js +16 -0
  40. package/dist/cli/program.js.map +1 -0
  41. package/dist/cli/register/basic-commands.d.ts +30 -0
  42. package/dist/cli/register/basic-commands.js +11 -0
  43. package/dist/cli/register/basic-commands.js.map +1 -0
  44. package/dist/cli/register/code-command.d.ts +3 -0
  45. package/dist/cli/register/code-command.js +5 -0
  46. package/dist/cli/register/code-command.js.map +1 -0
  47. package/dist/cli/register/restart-command.d.ts +3 -0
  48. package/dist/cli/register/restart-command.js +5 -0
  49. package/dist/cli/register/restart-command.js.map +1 -0
  50. package/dist/cli/register/start-command.d.ts +3 -0
  51. package/dist/cli/register/start-command.js +5 -0
  52. package/dist/cli/register/start-command.js.map +1 -0
  53. package/dist/cli/register/status-config-commands.d.ts +16 -0
  54. package/dist/cli/register/status-config-commands.js +7 -0
  55. package/dist/cli/register/status-config-commands.js.map +1 -0
  56. package/dist/cli/register/stop-command.d.ts +3 -0
  57. package/dist/cli/register/stop-command.js +5 -0
  58. package/dist/cli/register/stop-command.js.map +1 -0
  59. package/dist/cli/runtime.d.ts +5 -0
  60. package/dist/cli/runtime.js +11 -0
  61. package/dist/cli/runtime.js.map +1 -0
  62. package/dist/cli/server/port-utils.d.ts +52 -0
  63. package/dist/cli/server/port-utils.js +193 -0
  64. package/dist/cli/server/port-utils.js.map +1 -0
  65. package/dist/cli/spinner.d.ts +10 -0
  66. package/dist/cli/spinner.js +59 -0
  67. package/dist/cli/spinner.js.map +1 -0
  68. package/dist/cli/utils/normalize.d.ts +2 -0
  69. package/dist/cli/utils/normalize.js +22 -0
  70. package/dist/cli/utils/normalize.js.map +1 -0
  71. package/dist/cli/utils/safe-read-json.d.ts +1 -0
  72. package/dist/cli/utils/safe-read-json.js +11 -0
  73. package/dist/cli/utils/safe-read-json.js.map +1 -0
  74. package/dist/cli.js +149 -1738
  75. package/dist/cli.js.map +1 -1
  76. package/dist/client/anthropic/anthropic-protocol-client.js +4 -3
  77. package/dist/client/anthropic/anthropic-protocol-client.js.map +1 -1
  78. package/dist/client/gemini/gemini-protocol-client.js +5 -0
  79. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  80. package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +1 -1
  81. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +10 -3
  82. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  83. package/dist/commands/provider-update.js +355 -5
  84. package/dist/commands/provider-update.js.map +1 -1
  85. package/dist/commands/quota-daemon.js +2 -2
  86. package/dist/commands/quota-daemon.js.map +1 -1
  87. package/dist/config/provider-v2-loader.js +4 -2
  88. package/dist/config/provider-v2-loader.js.map +1 -1
  89. package/dist/docs/daemon-admin-ui.html +583 -87
  90. package/dist/index.js +32 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/manager/modules/quota/index.d.ts +19 -1
  93. package/dist/manager/modules/quota/index.js +130 -5
  94. package/dist/manager/modules/quota/index.js.map +1 -1
  95. package/dist/manager/modules/routing/index.js.map +1 -1
  96. package/dist/manager/storage/file-store.js +1 -1
  97. package/dist/manager/storage/file-store.js.map +1 -1
  98. package/dist/manager/types.d.ts +5 -0
  99. package/dist/providers/auth/oauth-lifecycle.js +2 -2
  100. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  101. package/dist/providers/core/api/provider-config.d.ts +2 -0
  102. package/dist/providers/core/api/provider-types.d.ts +2 -0
  103. package/dist/providers/core/config/service-profiles.js +1 -1
  104. package/dist/providers/core/config/service-profiles.js.map +1 -1
  105. package/dist/providers/core/runtime/base-provider.js +21 -27
  106. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  107. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +1 -0
  108. package/dist/providers/core/runtime/gemini-cli-http-provider.js +37 -6
  109. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  110. package/dist/providers/core/runtime/http-request-executor.js +23 -29
  111. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  112. package/dist/providers/core/runtime/http-transport-provider.js +46 -38
  113. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  114. package/dist/providers/core/utils/http-client.d.ts +9 -0
  115. package/dist/providers/core/utils/http-client.js +9 -11
  116. package/dist/providers/core/utils/http-client.js.map +1 -1
  117. package/dist/providers/core/utils/provider-error-reporter.js +2 -6
  118. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  119. package/dist/providers/mock/mock-provider-runtime.js +19 -5
  120. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  121. package/dist/server/handlers/handler-utils.d.ts +1 -1
  122. package/dist/server/handlers/handler-utils.js +4 -4
  123. package/dist/server/handlers/handler-utils.js.map +1 -1
  124. package/dist/server/handlers/responses-handler.js +2 -1
  125. package/dist/server/handlers/responses-handler.js.map +1 -1
  126. package/dist/server/handlers/sse-dispatcher.js +1 -4
  127. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  128. package/dist/server/runtime/http-server/colored-logger.d.ts +1 -1
  129. package/dist/server/runtime/http-server/colored-logger.js +22 -10
  130. package/dist/server/runtime/http-server/colored-logger.js.map +1 -1
  131. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +12 -6
  132. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  133. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +116 -98
  134. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  135. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js +108 -15
  136. package/dist/server/runtime/http-server/daemon-admin/quota-handler.js.map +1 -1
  137. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js +2 -1
  138. package/dist/server/runtime/http-server/daemon-admin/restart-handler.js.map +1 -1
  139. package/dist/server/runtime/http-server/daemon-admin/stats-handler.d.ts +3 -0
  140. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +56 -0
  141. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -0
  142. package/dist/server/runtime/http-server/daemon-admin/status-handler.js +8 -4
  143. package/dist/server/runtime/http-server/daemon-admin/status-handler.js.map +1 -1
  144. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +9 -0
  145. package/dist/server/runtime/http-server/daemon-admin-routes.js +3 -0
  146. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  147. package/dist/server/runtime/http-server/executor-provider.js +74 -0
  148. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  149. package/dist/server/runtime/http-server/index.d.ts +2 -0
  150. package/dist/server/runtime/http-server/index.js +107 -17
  151. package/dist/server/runtime/http-server/index.js.map +1 -1
  152. package/dist/server/runtime/http-server/request-executor.js +18 -11
  153. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  154. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  155. package/dist/server/runtime/http-server/routes.js +17 -4
  156. package/dist/server/runtime/http-server/routes.js.map +1 -1
  157. package/dist/server/runtime/http-server/stats-manager.d.ts +7 -0
  158. package/dist/server/runtime/http-server/stats-manager.js +31 -6
  159. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  160. package/dist/server/runtime/http-server/types.d.ts +5 -0
  161. package/dist/server/utils/http-error-mapper.js +70 -9
  162. package/dist/server/utils/http-error-mapper.js.map +1 -1
  163. package/dist/server/utils/request-id-manager.js +9 -5
  164. package/dist/server/utils/request-id-manager.js.map +1 -1
  165. package/dist/server/utils/sse-request-parser.js +2 -1
  166. package/dist/server/utils/sse-request-parser.js.map +1 -1
  167. package/dist/server/utils/utf8-chunk-buffer.d.ts +15 -30
  168. package/dist/server/utils/utf8-chunk-buffer.js +78 -88
  169. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -1
  170. package/dist/server/utils/warmup-storm-tracker.js +1 -1
  171. package/dist/server/utils/warmup-storm-tracker.js.map +1 -1
  172. package/dist/tools/provider-update/fetch-models.js +8 -5
  173. package/dist/tools/provider-update/fetch-models.js.map +1 -1
  174. package/dist/tools/provider-update/probe-context.d.ts +24 -0
  175. package/dist/tools/provider-update/probe-context.js +199 -0
  176. package/dist/tools/provider-update/probe-context.js.map +1 -0
  177. package/dist/tools/provider-update/types.d.ts +1 -0
  178. package/package.json +10 -4
  179. package/scripts/anthropic-compare-modes.mjs +40 -3
  180. package/scripts/antigravity-smoke.mjs +180 -0
  181. package/scripts/backfill-apply-patch-exec-errorsamples.mjs +225 -0
  182. package/scripts/compare-codex-rccx.mjs +59 -1
  183. package/scripts/compare-responses-request.mjs +50 -4
  184. package/scripts/lib/errorsamples.mjs +23 -0
  185. package/scripts/mock-provider/run-regressions.mjs +12 -2
  186. package/scripts/policy-violations-report.mjs +257 -0
  187. package/scripts/publish-rcc.mjs +16 -2
  188. package/scripts/scan-apply-patch-samples.mjs +148 -7
  189. package/scripts/tests/unified-hub-responses-enforce-safe.mjs +37 -0
  190. package/scripts/tests/unified-hub-shadow-regression.mjs +55 -0
  191. package/scripts/unified-hub-shadow-compare.mjs +359 -0
  192. package/scripts/verify-e2e-gemini-followup-sample.mjs +269 -0
  193. package/scripts/virtual-router-shadow-v2-real.mjs +71 -1
  194. package/scripts/virtual-router-shadow-v2.mjs +41 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Backfill apply_patch execution failures into ~/.routecodex/errorsamples/apply_patch_exec/**.
5
+ *
6
+ * Source: ~/.routecodex/codex-samples/** (stage snapshots)
7
+ * Signal: tool role message name=apply_patch and content includes "apply_patch verification failed".
8
+ *
9
+ * This is a best-effort collector for debugging. It does NOT attempt any semantic repair.
10
+ */
11
+
12
+ import fs from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import os from 'node:os';
15
+ import crypto from 'node:crypto';
16
+
17
+ const HOME = os.homedir();
18
+ const CODEX_ROOT_PRIMARY = path.join(HOME, '.routecodex', 'codex-samples');
19
+ const CODEX_ROOT_ALT = path.join(HOME, '.routecodex', 'codex samples');
20
+
21
+ const ERR_BASE =
22
+ process.env.ROUTECODEX_ERRORSAMPLES_DIR && process.env.ROUTECODEX_ERRORSAMPLES_DIR.trim().length
23
+ ? path.resolve(process.env.ROUTECODEX_ERRORSAMPLES_DIR)
24
+ : path.join(HOME, '.routecodex', 'errorsamples');
25
+ const OUT_ROOT = path.join(ERR_BASE, 'apply_patch_exec');
26
+
27
+ const MAX_PER_TYPE = 250;
28
+
29
+ function detectApplyPatchToolMode() {
30
+ return 'freeform';
31
+ }
32
+
33
+ function classifyExecutionFailure(content) {
34
+ const raw = String(content || '');
35
+ const trimmed = raw.trim();
36
+ const prefix = 'apply_patch verification failed:';
37
+ const msg = trimmed.toLowerCase().startsWith(prefix) ? trimmed.slice(prefix.length).trim() : trimmed;
38
+ const lower = msg.toLowerCase();
39
+
40
+ if (lower.includes('failed to read file')) return { errorType: 'read_file_failed', message: msg };
41
+ if (lower.includes('no such file') || lower.includes('file not found')) return { errorType: 'file_not_found', message: msg };
42
+ if (lower.includes('failed to find context')) return { errorType: 'context_not_found', message: msg };
43
+ if (lower.includes('failed to find expected lines')) return { errorType: 'expected_lines_not_found', message: msg };
44
+ if (lower.includes('invalid patch')) return { errorType: 'invalid_patch', message: msg };
45
+ if (lower.includes('failed to parse')) return { errorType: 'parse_failed', message: msg };
46
+
47
+ return { errorType: 'unknown', message: msg };
48
+ }
49
+
50
+ function stableId({ errorType, errorMessage, toolCallId, toolCallArgs, requestId, mode }) {
51
+ const key = `${String(errorType)}:${String(errorMessage)}:${String(toolCallId || '')}:${String(toolCallArgs || '')}:${String(
52
+ requestId || ''
53
+ )}:${String(mode || '')}`;
54
+ return crypto.createHash('sha1').update(key).digest('hex').slice(0, 16);
55
+ }
56
+
57
+ async function fileExists(p) {
58
+ try {
59
+ await fs.access(p);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ async function* walkJsonFiles(root) {
67
+ const stack = [root];
68
+ while (stack.length) {
69
+ const current = stack.pop();
70
+ let entries;
71
+ try {
72
+ entries = await fs.readdir(current, { withFileTypes: true });
73
+ } catch {
74
+ continue;
75
+ }
76
+ for (const entry of entries) {
77
+ const full = path.join(current, entry.name);
78
+ if (entry.isDirectory()) stack.push(full);
79
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) yield full;
80
+ }
81
+ }
82
+ }
83
+
84
+ async function ensureDir(p) {
85
+ await fs.mkdir(p, { recursive: true });
86
+ }
87
+
88
+ async function countJsonFiles(dir) {
89
+ try {
90
+ const entries = await fs.readdir(dir, { withFileTypes: true });
91
+ return entries.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.json')).length;
92
+ } catch {
93
+ return 0;
94
+ }
95
+ }
96
+
97
+ function buildToolCallArgsIndex(messages) {
98
+ const map = new Map();
99
+ for (const msg of messages) {
100
+ const toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
101
+ for (const tc of toolCalls) {
102
+ const id = typeof tc?.id === 'string' ? tc.id : '';
103
+ const fn = tc?.function;
104
+ const name = typeof fn?.name === 'string' ? String(fn.name).trim().toLowerCase() : '';
105
+ if (!id || name !== 'apply_patch') continue;
106
+ const args = typeof fn?.arguments === 'string' ? fn.arguments : '';
107
+ if (args) map.set(id, args);
108
+ }
109
+ }
110
+ return map;
111
+ }
112
+
113
+ async function main() {
114
+ const roots = [];
115
+ if (await fileExists(CODEX_ROOT_PRIMARY)) roots.push(CODEX_ROOT_PRIMARY);
116
+ if (await fileExists(CODEX_ROOT_ALT)) roots.push(CODEX_ROOT_ALT);
117
+
118
+ if (!roots.length) {
119
+ console.log('[backfill:apply_patch_exec] skip (no codex-samples root found)');
120
+ return;
121
+ }
122
+
123
+ await ensureDir(OUT_ROOT);
124
+
125
+ const mode = detectApplyPatchToolMode();
126
+
127
+ let scanned = 0;
128
+ let matchedFiles = 0;
129
+ let captured = 0;
130
+ let skippedLimit = 0;
131
+ let skippedExists = 0;
132
+
133
+ for (const root of roots) {
134
+ for await (const file of walkJsonFiles(root)) {
135
+ scanned += 1;
136
+ if (!file.endsWith('req_process_stage1_tool_governance.json')) continue;
137
+
138
+ let doc;
139
+ try {
140
+ doc = JSON.parse(await fs.readFile(file, 'utf-8'));
141
+ } catch {
142
+ continue;
143
+ }
144
+
145
+ const messages = Array.isArray(doc?.messages) ? doc.messages : [];
146
+ if (!messages.length) continue;
147
+
148
+ const toolCallArgsById = buildToolCallArgsIndex(messages);
149
+
150
+ const toolMsgs = messages.filter(
151
+ (m) =>
152
+ m &&
153
+ m.role === 'tool' &&
154
+ String(m.name || '').trim().toLowerCase() === 'apply_patch' &&
155
+ typeof m.content === 'string' &&
156
+ m.content.toLowerCase().includes('apply_patch verification failed')
157
+ );
158
+ if (!toolMsgs.length) continue;
159
+
160
+ matchedFiles += 1;
161
+
162
+ const metadata = doc?.metadata || {};
163
+ const requestId = typeof metadata?.requestId === 'string' ? metadata.requestId : undefined;
164
+ const entryEndpoint = typeof metadata?.originalEndpoint === 'string' ? metadata.originalEndpoint : undefined;
165
+ const providerKey = typeof metadata?.providerKey === 'string' ? metadata.providerKey : undefined;
166
+ const modelId = typeof doc?.model === 'string' ? doc.model : undefined;
167
+
168
+ for (const m of toolMsgs) {
169
+ const { errorType, message } = classifyExecutionFailure(m.content);
170
+ const safeType = String(errorType || 'unknown').replace(/[^a-z0-9-]/gi, '_');
171
+ const typeDir = path.join(OUT_ROOT, safeType);
172
+ await ensureDir(typeDir);
173
+
174
+ const currentCount = await countJsonFiles(typeDir);
175
+ if (currentCount >= MAX_PER_TYPE) {
176
+ skippedLimit += 1;
177
+ continue;
178
+ }
179
+
180
+ const toolCallId = typeof m.tool_call_id === 'string' ? m.tool_call_id : undefined;
181
+ const toolCallArgs = toolCallId ? toolCallArgsById.get(toolCallId) : undefined;
182
+ const id = `sample_${stableId({
183
+ errorType,
184
+ errorMessage: message,
185
+ toolCallId,
186
+ toolCallArgs,
187
+ requestId,
188
+ mode
189
+ })}`;
190
+ const outPath = path.join(typeDir, `${id}.json`);
191
+
192
+ if (await fileExists(outPath)) {
193
+ skippedExists += 1;
194
+ continue;
195
+ }
196
+
197
+ const payload = {
198
+ id,
199
+ timestamp: new Date().toISOString(),
200
+ errorType,
201
+ errorMessage: message,
202
+ toolCallId,
203
+ toolCallArgs,
204
+ requestId,
205
+ entryEndpoint,
206
+ providerKey,
207
+ model: modelId,
208
+ source: 'codex-samples:req_process_stage1_tool_governance',
209
+ meta: { applyPatchToolMode: mode, sourceFile: file }
210
+ };
211
+ await fs.writeFile(outPath, JSON.stringify(payload, null, 2), 'utf-8');
212
+ captured += 1;
213
+ }
214
+ }
215
+ }
216
+
217
+ console.log(
218
+ `[backfill:apply_patch_exec] scanned=${scanned} matchedFiles=${matchedFiles} captured=${captured} skippedExists=${skippedExists} skippedLimit=${skippedLimit} out=${OUT_ROOT}`
219
+ );
220
+ }
221
+
222
+ main().catch((err) => {
223
+ console.error('[backfill:apply_patch_exec] failed:', err?.stack || err?.message || String(err));
224
+ process.exit(2);
225
+ });
@@ -23,8 +23,10 @@
23
23
  */
24
24
 
25
25
  import fs from 'node:fs';
26
+ import os from 'node:os';
26
27
  import path from 'node:path';
27
28
  import { spawn } from 'node:child_process';
29
+ import { writeErrorSampleJson } from './lib/errorsamples.mjs';
28
30
 
29
31
  const DEFAULT_ROUTE_BASE = process.env.ROUTECODEX_BASE || 'http://127.0.0.1:5555';
30
32
  const DEFAULT_RCCX_BASE = process.env.RCCX_BASE || 'http://127.0.0.1:5556';
@@ -230,6 +232,29 @@ async function main() {
230
232
  rccxRunDir,
231
233
  rccxError: rccxResult.error || null
232
234
  });
235
+ try {
236
+ const file = await writeErrorSampleJson({
237
+ group: 'compare-codex-rccx',
238
+ kind: 'missing-artifacts',
239
+ payload: {
240
+ kind: 'compare-codex-rccx-missing-artifacts',
241
+ stamp: new Date().toISOString(),
242
+ samplePath,
243
+ requestId,
244
+ routeBase: opts.routeBase,
245
+ rccxBase: opts.rccxBase,
246
+ routeLabel: opts.routeLabel,
247
+ rccxLabel: opts.rccxLabel,
248
+ routeRunDir,
249
+ rccxRunDir,
250
+ routeError: routeResult.error || null,
251
+ rccxError: rccxResult.error || null
252
+ }
253
+ });
254
+ console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
255
+ } catch (err) {
256
+ console.error('[compare-codex-rccx] failed to write errorsample:', err);
257
+ }
233
258
  process.exitCode = 1;
234
259
  return;
235
260
  }
@@ -252,6 +277,33 @@ async function main() {
252
277
  // 为了调试 429 / 系列冷却问题,额外打印一小段 body 样本。
253
278
  console.log('[compare-codex-rccx] routecodex.bodySample =', routeResult.bodySample);
254
279
  console.log('[compare-codex-rccx] rccx.bodySample =', rccxResult.bodySample);
280
+ try {
281
+ const file = await writeErrorSampleJson({
282
+ group: 'compare-codex-rccx',
283
+ kind: 'mismatch',
284
+ payload: {
285
+ kind: 'compare-codex-rccx-mismatch',
286
+ stamp: new Date().toISOString(),
287
+ samplePath,
288
+ requestId,
289
+ routeBase: opts.routeBase,
290
+ rccxBase: opts.rccxBase,
291
+ routeLabel: opts.routeLabel,
292
+ rccxLabel: opts.rccxLabel,
293
+ routeRunDir,
294
+ rccxRunDir,
295
+ routeMeta,
296
+ rccxMeta,
297
+ routeBodyKind: routeResult.bodyKind,
298
+ rccxBodyKind: rccxResult.bodyKind,
299
+ routeBodySample: routeResult.bodySample,
300
+ rccxBodySample: rccxResult.bodySample
301
+ }
302
+ });
303
+ console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
304
+ } catch (err) {
305
+ console.error('[compare-codex-rccx] failed to write errorsample:', err);
306
+ }
255
307
  process.exitCode = 1;
256
308
  return;
257
309
  }
@@ -263,6 +315,12 @@ async function main() {
263
315
 
264
316
  main().catch((err) => {
265
317
  console.error('[compare-codex-rccx] fatal error:', err);
318
+ try {
319
+ const root = path.join(os.homedir(), '.routecodex', 'errorsamples', 'compare-codex-rccx');
320
+ const file = path.join(root, `fatal-${Date.now()}.txt`);
321
+ fs.mkdirSync(root, { recursive: true });
322
+ fs.writeFileSync(file, String(err?.stack || err), 'utf8');
323
+ console.error(`[compare-codex-rccx] wrote errorsample: ${file}`);
324
+ } catch {}
266
325
  process.exitCode = 1;
267
326
  });
268
-
@@ -10,6 +10,7 @@
10
10
  import fs from 'node:fs';
11
11
  import os from 'node:os';
12
12
  import path from 'node:path';
13
+ import { writeErrorSampleJson } from './lib/errorsamples.mjs';
13
14
 
14
15
  const protocolFolders = {
15
16
  'openai-responses': 'openai-responses',
@@ -118,9 +119,15 @@ function listCounts(label, payload) {
118
119
  console.log(
119
120
  `${label} counts → input:${inputCount} messages:${messageCount} tools:${toolCount} instructions:${typeof payload?.instructions === 'string'}`
120
121
  );
122
+ return {
123
+ input: inputCount,
124
+ messages: messageCount,
125
+ tools: toolCount,
126
+ hasInstructions: typeof payload?.instructions === 'string'
127
+ };
121
128
  }
122
129
 
123
- function main() {
130
+ async function main() {
124
131
  const opts = parseArgs();
125
132
  const clientProtocol = opts.clientProtocol || opts.protocol;
126
133
  const providerProtocol = opts.providerProtocol || opts.protocol;
@@ -146,8 +153,8 @@ function main() {
146
153
  const clientPayload = extractPayload(clientSnapshot);
147
154
  const providerPayload = extractPayload(providerSnapshot);
148
155
 
149
- listCounts('Client', clientPayload);
150
- listCounts('Provider', providerPayload);
156
+ const clientCounts = listCounts('Client', clientPayload);
157
+ const providerCounts = listCounts('Provider', providerPayload);
151
158
 
152
159
  const diffs = diffPayloads(pruneUndefined(clientPayload), pruneUndefined(providerPayload));
153
160
  if (!diffs.length) {
@@ -160,6 +167,30 @@ function main() {
160
167
  if (diffs.length > 50) {
161
168
  console.log(` ... ${diffs.length - 50} more differences`);
162
169
  }
170
+
171
+ const record = {
172
+ kind: 'compare-responses-request-diff',
173
+ requestId: normalizedId,
174
+ clientProtocol,
175
+ providerProtocol,
176
+ clientPath,
177
+ providerPath,
178
+ clientCounts,
179
+ providerCounts,
180
+ diffsCount: diffs.length,
181
+ diffsHead: diffs.slice(0, 200),
182
+ diffsTruncated: diffs.length > 200
183
+ };
184
+ try {
185
+ const file = await writeErrorSampleJson({
186
+ group: 'compare-responses-request',
187
+ kind: 'diff',
188
+ payload: record
189
+ });
190
+ console.error(`[compare-responses-request] wrote errorsample: ${file}`);
191
+ } catch (err) {
192
+ console.error('[compare-responses-request] failed to write errorsample:', err);
193
+ }
163
194
  }
164
195
  }
165
196
 
@@ -217,4 +248,19 @@ function diffPayloads(expected, actual, path = '<root>') {
217
248
  return [{ path, expected, actual }];
218
249
  }
219
250
 
220
- main();
251
+ main().catch((err) => {
252
+ console.error('[compare-responses-request] fatal error:', err);
253
+ void writeErrorSampleJson({
254
+ group: 'compare-responses-request',
255
+ kind: 'fatal',
256
+ payload: {
257
+ kind: 'compare-responses-request-fatal',
258
+ stamp: new Date().toISOString(),
259
+ argv: process.argv.slice(2),
260
+ error: String(err?.stack || err)
261
+ }
262
+ }).then((file) => {
263
+ console.error(`[compare-responses-request] wrote errorsample: ${file}`);
264
+ }).catch(() => {});
265
+ process.exitCode = 1;
266
+ });
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ function safeStamp() {
6
+ const iso = new Date().toISOString();
7
+ // 2026-01-18T12:34:56.789Z -> 20260118-123456-789Z
8
+ return iso.replace(/[-:]/g, '').replace('T', '-').replace('.', '-');
9
+ }
10
+
11
+ function safeName(name) {
12
+ return String(name || 'sample').replace(/[^\w.-]/g, '_');
13
+ }
14
+
15
+ export async function writeErrorSampleJson({ group, kind, payload }) {
16
+ const root = path.join(os.homedir(), '.routecodex', 'errorsamples');
17
+ const dir = path.join(root, safeName(group));
18
+ await fs.mkdir(dir, { recursive: true });
19
+ const file = path.join(dir, `${safeName(kind)}-${safeStamp()}-${Math.random().toString(16).slice(2)}.json`);
20
+ await fs.writeFile(file, JSON.stringify(payload, null, 2), 'utf8');
21
+ return file;
22
+ }
23
+
@@ -188,7 +188,7 @@ async function writeTempConfig(sample, port) {
188
188
  return { dir, file };
189
189
  }
190
190
 
191
- function createServer(configPath, port, snapshotRoot) {
191
+ async function createServer(configPath, port, snapshotRoot) {
192
192
  const env = {
193
193
  ...process.env,
194
194
  ROUTECODEX_USE_MOCK: '1',
@@ -207,6 +207,16 @@ function createServer(configPath, port, snapshotRoot) {
207
207
  : {})
208
208
  };
209
209
  const entry = path.join(PROJECT_ROOT, 'dist', 'index.js');
210
+ // Defensive: build scripts or other verification steps may clean dist between checks.
211
+ // Ensure the server entry exists right before spawning.
212
+ // (Avoids confusing "Cannot find module dist/index.js" regressions.)
213
+ if (!(await fileExists(entry))) {
214
+ console.warn('[mock:regressions] dist/index.js missing at spawn time, rebuilding via "npm run build:min"...');
215
+ await runBuildForMockRegressions();
216
+ if (!(await fileExists(entry))) {
217
+ throw new Error(`dist/index.js still missing after rebuild: ${entry}`);
218
+ }
219
+ }
210
220
  const child = spawn(process.execPath, [entry], {
211
221
  cwd: PROJECT_ROOT,
212
222
  env,
@@ -465,7 +475,7 @@ async function runSample(sample, index) {
465
475
  const { dir, file } = await writeTempConfig(sample, port);
466
476
  // 为当前样本创建独立的临时快照根目录,并在完成后整体删除
467
477
  const snapshotRoot = path.join(dir, 'codex-samples');
468
- const server = createServer(file, port, snapshotRoot);
478
+ const server = await createServer(file, port, snapshotRoot);
469
479
  const tags = new Set(Array.isArray(sample.tags) ? sample.tags : []);
470
480
  const expectSseTerminationError = tags.has('responses_sse_terminated');
471
481
  const allowSampleError =