@jsonstudio/rcc 0.89.1136 → 0.89.1205

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 (145) 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 +148 -1775
  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-cli/gemini-cli-protocol-client.d.ts +1 -1
  79. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +10 -3
  80. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  81. package/dist/commands/quota-daemon.js +2 -2
  82. package/dist/commands/quota-daemon.js.map +1 -1
  83. package/dist/config/provider-v2-loader.js +4 -2
  84. package/dist/config/provider-v2-loader.js.map +1 -1
  85. package/dist/manager/modules/quota/index.js +21 -4
  86. package/dist/manager/modules/quota/index.js.map +1 -1
  87. package/dist/manager/modules/routing/index.js.map +1 -1
  88. package/dist/manager/storage/file-store.js +1 -1
  89. package/dist/manager/storage/file-store.js.map +1 -1
  90. package/dist/modules/llmswitch/bridge.js +45 -1
  91. package/dist/modules/llmswitch/bridge.js.map +1 -1
  92. package/dist/providers/auth/oauth-lifecycle.js +2 -2
  93. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  94. package/dist/providers/core/api/provider-config.d.ts +2 -0
  95. package/dist/providers/core/api/provider-types.d.ts +2 -0
  96. package/dist/providers/core/runtime/base-provider.js +21 -27
  97. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  98. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +1 -0
  99. package/dist/providers/core/runtime/gemini-cli-http-provider.js +37 -5
  100. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  101. package/dist/providers/core/runtime/http-request-executor.js +23 -29
  102. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  103. package/dist/providers/core/runtime/http-transport-provider.js +20 -0
  104. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  105. package/dist/providers/core/utils/http-client.d.ts +9 -0
  106. package/dist/providers/core/utils/http-client.js +9 -11
  107. package/dist/providers/core/utils/http-client.js.map +1 -1
  108. package/dist/providers/core/utils/provider-error-reporter.js +2 -6
  109. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  110. package/dist/providers/mock/mock-provider-runtime.js +19 -5
  111. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  112. package/dist/server/runtime/http-server/hub-shadow-compare.d.ts +18 -0
  113. package/dist/server/runtime/http-server/hub-shadow-compare.js +180 -0
  114. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -0
  115. package/dist/server/runtime/http-server/index.d.ts +4 -0
  116. package/dist/server/runtime/http-server/index.js +202 -11
  117. package/dist/server/runtime/http-server/index.js.map +1 -1
  118. package/dist/server/runtime/http-server/request-executor.js +9 -1
  119. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  120. package/dist/server/runtime/http-server/routes.js +8 -4
  121. package/dist/server/runtime/http-server/routes.js.map +1 -1
  122. package/dist/server/runtime/http-server/stats-manager.js +9 -3
  123. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  124. package/dist/utils/errorsamples.d.ts +5 -0
  125. package/dist/utils/errorsamples.js +27 -0
  126. package/dist/utils/errorsamples.js.map +1 -0
  127. package/dist/utils/runtime-versions.d.ts +1 -0
  128. package/dist/utils/runtime-versions.js +38 -0
  129. package/dist/utils/runtime-versions.js.map +1 -0
  130. package/package.json +10 -4
  131. package/scripts/anthropic-compare-modes.mjs +40 -3
  132. package/scripts/antigravity-smoke.mjs +180 -0
  133. package/scripts/backfill-apply-patch-exec-errorsamples.mjs +225 -0
  134. package/scripts/compare-codex-rccx.mjs +59 -1
  135. package/scripts/compare-responses-request.mjs +50 -4
  136. package/scripts/lib/errorsamples.mjs +23 -0
  137. package/scripts/mock-provider/run-regressions.mjs +12 -2
  138. package/scripts/policy-violations-report.mjs +257 -0
  139. package/scripts/publish-rcc.mjs +16 -2
  140. package/scripts/tests/unified-hub-responses-enforce-safe.mjs +37 -0
  141. package/scripts/tests/unified-hub-shadow-regression.mjs +55 -0
  142. package/scripts/unified-hub-shadow-compare.mjs +359 -0
  143. package/scripts/verify-e2e-gemini-followup-sample.mjs +269 -0
  144. package/scripts/virtual-router-shadow-v2-real.mjs +71 -1
  145. package/scripts/virtual-router-shadow-v2.mjs +41 -0
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Antigravity upstream smoke test (direct provider call).
4
+ *
5
+ * Purpose:
6
+ * - Hit Antigravity (Cloud Code Assist) upstream using Gemini CLI HTTP provider.
7
+ * - Validate request shaping is accepted (especially "no body.request.request.*").
8
+ *
9
+ * Usage:
10
+ * node scripts/antigravity-smoke.mjs
11
+ *
12
+ * Required:
13
+ * - A valid Antigravity OAuth token file (JSON), e.g.
14
+ * ~/.routecodex/auth/antigravity-oauth-1-<alias>.json
15
+ * - Export env:
16
+ * ANTIGRAVITY_TOKEN_FILE=/absolute/path/to/token.json
17
+ *
18
+ * Optional:
19
+ * ANTIGRAVITY_BASEURL=https://daily-cloudcode-pa.sandbox.googleapis.com
20
+ * ANTIGRAVITY_MODEL=gemini-3-pro-high
21
+ */
22
+
23
+ import fs from 'node:fs';
24
+ import os from 'node:os';
25
+ import path from 'node:path';
26
+
27
+ import { GeminiCLIHttpProvider } from '../dist/providers/core/runtime/gemini-cli-http-provider.js';
28
+ import { GeminiSseToJsonConverter } from '../sharedmodule/llmswitch-core/dist/sse/sse-to-json/index.js';
29
+
30
+ function resolveTokenFile() {
31
+ const raw =
32
+ (process.env.ANTIGRAVITY_TOKEN_FILE && process.env.ANTIGRAVITY_TOKEN_FILE.trim()) ||
33
+ (process.env.ROUTECODEX_ANTIGRAVITY_TOKEN_FILE && process.env.ROUTECODEX_ANTIGRAVITY_TOKEN_FILE.trim()) ||
34
+ (process.env.RCC_ANTIGRAVITY_TOKEN_FILE && process.env.RCC_ANTIGRAVITY_TOKEN_FILE.trim()) ||
35
+ '';
36
+ if (raw) {
37
+ const expanded = raw.startsWith('~/') ? path.join(os.homedir(), raw.slice(2)) : raw;
38
+ return path.isAbsolute(expanded) ? expanded : path.resolve(expanded);
39
+ }
40
+ const authDir = path.join(os.homedir(), '.routecodex', 'auth');
41
+ const defaultPath = path.join(authDir, 'antigravity-oauth.json');
42
+ if (fs.existsSync(defaultPath)) {
43
+ return defaultPath;
44
+ }
45
+ try {
46
+ const candidates = fs
47
+ .readdirSync(authDir)
48
+ .filter((name) => name.startsWith('antigravity-oauth-') && name.endsWith('.json'))
49
+ .map((name) => path.join(authDir, name));
50
+ if (!candidates.length) {
51
+ return defaultPath;
52
+ }
53
+ candidates.sort((a, b) => {
54
+ try {
55
+ return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs;
56
+ } catch {
57
+ return 0;
58
+ }
59
+ });
60
+ return candidates[0];
61
+ } catch {
62
+ return defaultPath;
63
+ }
64
+ }
65
+
66
+ function extractTextFromGeminiResponse(payload) {
67
+ const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
68
+ const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : null;
69
+ const parts = primary?.content?.parts;
70
+ if (!Array.isArray(parts)) return '';
71
+ return parts
72
+ .map((p) => (p && typeof p === 'object' && typeof p.text === 'string' ? p.text : ''))
73
+ .filter((s) => s.trim().length > 0)
74
+ .join('')
75
+ .trim();
76
+ }
77
+
78
+ async function decodeGeminiSse(stream, requestId) {
79
+ const converter = new GeminiSseToJsonConverter();
80
+ return await converter.convertSseToJson(stream, { requestId });
81
+ }
82
+
83
+ async function main() {
84
+ const tokenFile = resolveTokenFile();
85
+ if (!fs.existsSync(tokenFile)) {
86
+ console.error(`[antigravity-smoke] Missing token file: ${tokenFile}`);
87
+ console.error('[antigravity-smoke] Create one via: `node scripts/auth-antigravity-token.mjs`');
88
+ process.exit(2);
89
+ }
90
+
91
+ const baseUrl =
92
+ (process.env.ANTIGRAVITY_BASEURL && process.env.ANTIGRAVITY_BASEURL.trim()) ||
93
+ 'https://daily-cloudcode-pa.sandbox.googleapis.com';
94
+ const model =
95
+ (process.env.ANTIGRAVITY_MODEL && process.env.ANTIGRAVITY_MODEL.trim()) || 'gemini-3-pro-high';
96
+
97
+ const config = {
98
+ id: 'antigravity-smoke',
99
+ config: {
100
+ // IMPORTANT: Antigravity uses Gemini CLI protocol (Cloud Code Assist v1internal).
101
+ providerType: 'gemini',
102
+ providerId: 'antigravity',
103
+ baseUrl,
104
+ auth: {
105
+ type: 'antigravity-oauth',
106
+ apiKey: '',
107
+ tokenFile
108
+ },
109
+ overrides: { maxRetries: 0 }
110
+ }
111
+ };
112
+
113
+ const dependencies = {
114
+ logger: { logModule: () => {}, logProviderRequest: () => {} },
115
+ errorHandlingCenter: { handleError: async () => {} }
116
+ };
117
+
118
+ const provider = new GeminiCLIHttpProvider(config, dependencies);
119
+ await provider.initialize();
120
+
121
+ const buildPayload = ({ nestedRequest }) => {
122
+ const core = {
123
+ model,
124
+ contents: [{ role: 'user', parts: [{ text: 'Reply with exactly: pong' }] }],
125
+ // Antigravity agent runtime may spend a portion of maxOutputTokens on internal thoughts.
126
+ // Use a sufficiently high value so we can reliably observe a visible text response.
127
+ generationConfig: { maxOutputTokens: 256 }
128
+ };
129
+ if (nestedRequest) {
130
+ // This intentionally mimics an illegal intermediate shape that used to produce
131
+ // `body.request.request.*` after protocol-client wrapping. Provider preprocess
132
+ // must flatten it before sending upstream.
133
+ return { model, request: { contents: core.contents, generationConfig: core.generationConfig } };
134
+ }
135
+ return core;
136
+ };
137
+
138
+ for (const variant of [
139
+ { name: 'top_level_contents', nestedRequest: false },
140
+ { name: 'nested_request_container', nestedRequest: true }
141
+ ]) {
142
+ const maxAttempts = 3;
143
+ let lastDecoded = null;
144
+ let okText = '';
145
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
146
+ const requestId = `antigravity-smoke-${variant.name}-a${attempt}-${Date.now()}`;
147
+ const request = { data: buildPayload(variant) };
148
+ const res = await provider.sendRequest(request);
149
+ const stream = res?.__sse_responses || res?.data?.__sse_responses;
150
+ if (!stream) {
151
+ console.error(`[antigravity-smoke] Missing SSE stream for ${variant.name} (attempt ${attempt}/${maxAttempts})`);
152
+ console.error(JSON.stringify(res?.data ?? res).slice(0, 800));
153
+ process.exit(3);
154
+ }
155
+ const decoded = await decodeGeminiSse(stream, requestId);
156
+ lastDecoded = decoded;
157
+ const text = extractTextFromGeminiResponse(decoded);
158
+ if (text && text.toLowerCase().includes('pong')) {
159
+ okText = text;
160
+ break;
161
+ }
162
+ if (attempt < maxAttempts) {
163
+ await new Promise((r) => setTimeout(r, 250));
164
+ }
165
+ }
166
+ if (!okText) {
167
+ console.error(`[antigravity-smoke] Unexpected response for ${variant.name}`);
168
+ console.error(JSON.stringify(lastDecoded).slice(0, 1200));
169
+ process.exit(4);
170
+ }
171
+ console.log(`[antigravity-smoke] OK ${variant.name}: ${JSON.stringify(okText).slice(0, 120)}`);
172
+ }
173
+
174
+ console.log('[antigravity-smoke] done');
175
+ }
176
+
177
+ main().catch((err) => {
178
+ console.error('[antigravity-smoke] Error:', err instanceof Error ? err.stack || err.message : String(err));
179
+ process.exit(1);
180
+ });
@@ -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 =