@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,257 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Report Unified Hub policy violations/enforcement rewrites captured under:
4
+ * ~/.routecodex/codex-samples/__policy_violations__/
5
+ *
6
+ * This is intended for day-to-day monitoring when policy is enabled by default.
7
+ */
8
+
9
+ import fs from 'node:fs/promises';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+
13
+ function usage() {
14
+ console.log(`Usage:
15
+ node scripts/policy-violations-report.mjs [options]
16
+
17
+ Options:
18
+ --root <dir> default: ~/.routecodex/codex-samples/__policy_violations__
19
+ --since-hours <n> only include files modified in last N hours
20
+ --limit <n> limit printed rows per section (default: 30)
21
+ --fail exit 1 if any records found
22
+ --help show help
23
+ `);
24
+ }
25
+
26
+ function parseArgs(argv) {
27
+ const out = {
28
+ root: path.join(os.homedir(), '.routecodex', 'errorsamples', 'policy'),
29
+ sinceHours: undefined,
30
+ limit: 30,
31
+ fail: false
32
+ };
33
+ for (let i = 2; i < argv.length; i += 1) {
34
+ const a = argv[i];
35
+ if (a === '--root' && i + 1 < argv.length) out.root = argv[++i];
36
+ else if (a === '--since-hours' && i + 1 < argv.length) out.sinceHours = Number(argv[++i]);
37
+ else if (a === '--limit' && i + 1 < argv.length) out.limit = Number(argv[++i]);
38
+ else if (a === '--fail') out.fail = true;
39
+ else if (a === '--help' || a === '-h') out.help = true;
40
+ else {
41
+ console.error(`Unknown arg: ${a}`);
42
+ out.help = true;
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+
48
+ async function fileExists(p) {
49
+ try {
50
+ await fs.access(p);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ async function walk(dir) {
58
+ const out = [];
59
+ const stack = [dir];
60
+ while (stack.length) {
61
+ const current = stack.pop();
62
+ let entries = [];
63
+ try {
64
+ entries = await fs.readdir(current, { withFileTypes: true });
65
+ } catch {
66
+ continue;
67
+ }
68
+ for (const ent of entries) {
69
+ const p = path.join(current, ent.name);
70
+ if (ent.isDirectory()) stack.push(p);
71
+ else if (ent.isFile() && ent.name.endsWith('.json')) out.push(p);
72
+ }
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function inc(map, key, by = 1) {
78
+ map.set(key, (map.get(key) || 0) + by);
79
+ }
80
+
81
+ function topN(map, n) {
82
+ return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, n);
83
+ }
84
+
85
+ async function readJson(p) {
86
+ try {
87
+ const raw = await fs.readFile(p, 'utf8');
88
+ return JSON.parse(raw);
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ function classifyRecord(obj) {
95
+ if (!obj || typeof obj !== 'object') return { kind: 'unknown' };
96
+ const o = obj;
97
+ if (Array.isArray(o.violations) || (o.summary && typeof o.summary === 'object')) return { kind: 'observe' };
98
+ if (Array.isArray(o.removedTopLevelKeys) || Array.isArray(o.flattenedWrappers)) return { kind: 'enforce' };
99
+ return { kind: 'unknown' };
100
+ }
101
+
102
+ function safeString(value) {
103
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
104
+ }
105
+
106
+ function inferStageFromFilename(filePath) {
107
+ const base = path.basename(filePath, '.json');
108
+ return base.replace(/_[0-9]+$/, '');
109
+ }
110
+
111
+ function fmtRow(cols, widths) {
112
+ return cols
113
+ .map((c, i) => {
114
+ const w = widths[i] || 20;
115
+ const s = String(c ?? '');
116
+ return s.length > w ? `${s.slice(0, Math.max(0, w - 1))}…` : s.padEnd(w, ' ');
117
+ })
118
+ .join(' ');
119
+ }
120
+
121
+ async function main() {
122
+ const args = parseArgs(process.argv);
123
+ if (args.help) {
124
+ usage();
125
+ process.exit(0);
126
+ }
127
+ let root = path.resolve(args.root);
128
+ if (!(await fileExists(root))) {
129
+ const fallback = path.join(os.homedir(), '.routecodex', 'codex-samples', '__policy_violations__');
130
+ if (await fileExists(fallback)) {
131
+ root = fallback;
132
+ } else {
133
+ console.log(`[policy-report] no folder: ${root}`);
134
+ process.exit(0);
135
+ }
136
+ }
137
+
138
+ const sinceMs =
139
+ typeof args.sinceHours === 'number' && Number.isFinite(args.sinceHours) && args.sinceHours > 0
140
+ ? Date.now() - args.sinceHours * 60 * 60 * 1000
141
+ : null;
142
+
143
+ const files = await walk(root);
144
+ const rows = [];
145
+ for (const file of files) {
146
+ let st;
147
+ try {
148
+ st = await fs.stat(file);
149
+ } catch {
150
+ continue;
151
+ }
152
+ if (sinceMs !== null && st.mtimeMs < sinceMs) continue;
153
+ const obj = await readJson(file);
154
+ if (!obj) continue;
155
+ const rel = path.relative(root, file);
156
+ const parts = rel.split(path.sep);
157
+ const endpointFolder = parts[0] || '';
158
+ const providerKey = parts[1] || '';
159
+ const requestId = parts[2] || '';
160
+ rows.push({
161
+ file,
162
+ rel,
163
+ endpointFolder,
164
+ providerKey,
165
+ requestId,
166
+ stage: safeString(obj?.stage) || safeString(obj?.meta?.stage) || inferStageFromFilename(file),
167
+ protocol: safeString(obj?.providerProtocol) || safeString(obj?.protocol),
168
+ kind: classifyRecord(obj).kind,
169
+ obj
170
+ });
171
+ }
172
+
173
+ console.log(`[policy-report] root=${root}`);
174
+ if (sinceMs !== null) {
175
+ console.log(`[policy-report] sinceHours=${args.sinceHours}`);
176
+ }
177
+ console.log(`[policy-report] records=${rows.length}`);
178
+ if (!rows.length) {
179
+ process.exit(0);
180
+ }
181
+
182
+ const byStage = new Map();
183
+ const byProtocol = new Map();
184
+ const violationPathCounts = new Map();
185
+ const wrapperCounts = new Map();
186
+ const removedKeyCounts = new Map();
187
+
188
+ for (const r of rows) {
189
+ inc(byStage, r.stage);
190
+ if (r.protocol) inc(byProtocol, r.protocol);
191
+
192
+ if (r.kind === 'observe' && Array.isArray(r.obj?.violations)) {
193
+ for (const v of r.obj.violations) {
194
+ const p = safeString(v?.path) || '(unknown)';
195
+ inc(violationPathCounts, p);
196
+ }
197
+ }
198
+ if (r.kind === 'enforce') {
199
+ const flattened = Array.isArray(r.obj?.flattenedWrappers) ? r.obj.flattenedWrappers : [];
200
+ for (const w of flattened) inc(wrapperCounts, String(w));
201
+ const removed = Array.isArray(r.obj?.removedTopLevelKeys) ? r.obj.removedTopLevelKeys : [];
202
+ for (const k of removed) inc(removedKeyCounts, String(k));
203
+ }
204
+ }
205
+
206
+ const limit = Number.isFinite(args.limit) && args.limit > 0 ? args.limit : 30;
207
+
208
+ console.log('\n[policy-report] top stages:');
209
+ for (const [k, v] of topN(byStage, limit)) {
210
+ console.log(`- ${k}: ${v}`);
211
+ }
212
+
213
+ console.log('\n[policy-report] top protocols:');
214
+ for (const [k, v] of topN(byProtocol, limit)) {
215
+ console.log(`- ${k}: ${v}`);
216
+ }
217
+
218
+ if (violationPathCounts.size) {
219
+ console.log('\n[policy-report] top violation paths:');
220
+ for (const [k, v] of topN(violationPathCounts, limit)) {
221
+ console.log(`- ${k}: ${v}`);
222
+ }
223
+ }
224
+
225
+ if (wrapperCounts.size) {
226
+ console.log('\n[policy-report] top flattened wrappers (enforce):');
227
+ for (const [k, v] of topN(wrapperCounts, limit)) {
228
+ console.log(`- ${k}: ${v}`);
229
+ }
230
+ }
231
+
232
+ if (removedKeyCounts.size) {
233
+ console.log('\n[policy-report] top removed keys (enforce):');
234
+ for (const [k, v] of topN(removedKeyCounts, limit)) {
235
+ console.log(`- ${k}: ${v}`);
236
+ }
237
+ }
238
+
239
+ console.log('\n[policy-report] newest records:');
240
+ const newest = rows
241
+ .slice()
242
+ .sort((a, b) => (a.file < b.file ? 1 : -1))
243
+ .slice(0, Math.min(limit, rows.length));
244
+ console.log(fmtRow(['endpoint', 'providerKey', 'stage', 'protocol', 'requestId'], [16, 28, 40, 18, 24]));
245
+ for (const r of newest) {
246
+ console.log(fmtRow([r.endpointFolder, r.providerKey, r.stage, r.protocol || '-', r.requestId], [16, 28, 40, 18, 24]));
247
+ }
248
+
249
+ if (args.fail) {
250
+ process.exit(1);
251
+ }
252
+ }
253
+
254
+ main().catch((err) => {
255
+ console.error('[policy-report] failed:', err);
256
+ process.exit(2);
257
+ });
@@ -8,6 +8,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
8
  const PROJECT_ROOT = path.resolve(__dirname, '..');
9
9
  const PACK_SCRIPT = path.join(PROJECT_ROOT, 'scripts', 'pack-mode.mjs');
10
10
  const pkgPath = path.join(PROJECT_ROOT, 'package.json');
11
+ const llmsPath = path.join(PROJECT_ROOT, 'node_modules', '@jsonstudio', 'llms');
11
12
 
12
13
  function run(command, args, options = {}) {
13
14
  const res = spawnSync(command, args, { stdio: 'inherit', ...options });
@@ -17,6 +18,14 @@ function run(command, args, options = {}) {
17
18
  }
18
19
 
19
20
  try {
21
+ const hadDevLink = (() => {
22
+ try {
23
+ return fs.lstatSync(llmsPath).isSymbolicLink();
24
+ } catch {
25
+ return false;
26
+ }
27
+ })();
28
+
20
29
  // 1) 使用 release 模式构建 dist(依赖 npm 上的 @jsonstudio/llms)
21
30
  run('npm', ['run', 'build:min'], {
22
31
  cwd: PROJECT_ROOT,
@@ -39,8 +48,13 @@ try {
39
48
  // 3) 发布 npm 包
40
49
  run('npm', ['publish', tarballName], { cwd: PROJECT_ROOT });
41
50
 
42
- // 4) pack-mode 会在内部检测 dev 链接并调用 ensure-llmswitch-mode 恢复 dev 模式,
43
- // 因此此处不再额外修改 BUILD_MODE 或重新 link。后续本地如需 dev build,可单独运行 `npm run build:dev`。
51
+ // 4) 发布结束后恢复 dev 模式(routecodex 约定始终为 dev CLI;rcc 发布时才切 release)。
52
+ if (hadDevLink) {
53
+ run('npm', ['run', 'llmswitch:ensure'], {
54
+ cwd: PROJECT_ROOT,
55
+ env: { ...process.env, BUILD_MODE: 'dev' }
56
+ });
57
+ }
44
58
  } catch (err) {
45
59
  console.error('[publish-rcc] failed:', err.message);
46
60
  process.exit(1);
@@ -17,6 +17,7 @@
17
17
  import fs from 'node:fs/promises';
18
18
  import path from 'node:path';
19
19
  import os from 'node:os';
20
+ import crypto from 'node:crypto';
20
21
  import { fileURLToPath, pathToFileURL } from 'node:url';
21
22
 
22
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -27,8 +28,13 @@ const coreLoaderUrl = pathToFileURL(coreLoaderPath).href;
27
28
 
28
29
  const HOME = os.homedir();
29
30
  const USER_CODEX_ROOT = path.join(HOME, '.routecodex', 'codex-samples');
31
+ const USER_CODEX_ROOT_ALT = path.join(HOME, '.routecodex', 'codex samples');
30
32
  const USER_CODEX_PENDING = path.join(USER_CODEX_ROOT, 'openai-chat', '__pending__');
31
33
  const REPO_GOLDENS_ROOT = path.join(repoRoot, 'samples', 'ci-goldens');
34
+ const ERROR_SAMPLES_ROOT = path.join(HOME, '.routecodex', 'errorsamples', 'apply_patch');
35
+
36
+ const DEFAULT_CAPTURE_TOTAL_LIMIT = 5000;
37
+ const DEFAULT_CAPTURE_PER_REASON_LIMIT = 250;
32
38
 
33
39
  async function fileExists(p) {
34
40
  try {
@@ -111,6 +117,74 @@ function extractApplyPatchArgs(doc, { allowRegressionOriginalArgs } = { allowReg
111
117
  return out;
112
118
  }
113
119
 
120
+ function isContextMismatchReason(reason) {
121
+ // We only capture/repair shape issues; context mismatches are not actionable here.
122
+ // Keep this conservative: if a new reason is introduced, we still capture it unless
123
+ // it clearly indicates a context mismatch.
124
+ if (!reason) return false;
125
+ const r = String(reason);
126
+ return (
127
+ r.includes('context') ||
128
+ r.includes('expected') ||
129
+ r.includes('hunk') ||
130
+ r.includes('no_match') ||
131
+ r.includes('not_found')
132
+ );
133
+ }
134
+
135
+ function stableHash(input) {
136
+ return crypto.createHash('sha256').update(input).digest('hex').slice(0, 16);
137
+ }
138
+
139
+ async function captureFailure({
140
+ destRoot,
141
+ label,
142
+ sourceFile,
143
+ reason,
144
+ originalArgs,
145
+ validationResult,
146
+ captureState,
147
+ }) {
148
+ if (isContextMismatchReason(reason)) return;
149
+ if (captureState?.reasonAllowList && !captureState.reasonAllowList.has(reason)) return;
150
+ if (!destRoot) return;
151
+
152
+ const totalLimit = captureState.totalLimit ?? DEFAULT_CAPTURE_TOTAL_LIMIT;
153
+ const perReasonLimit = captureState.perReasonLimit ?? DEFAULT_CAPTURE_PER_REASON_LIMIT;
154
+ if (captureState.totalCaptured >= totalLimit) return;
155
+
156
+ const currentReasonCount = captureState.byReason.get(reason) ?? 0;
157
+ if (currentReasonCount >= perReasonLimit) return;
158
+
159
+ const key = `${label}\n${reason}\n${sourceFile}\n${originalArgs}`;
160
+ const filename = `sample_${stableHash(key)}.json`;
161
+ const folder = path.join(destRoot, reason);
162
+ const outPath = path.join(folder, filename);
163
+
164
+ try {
165
+ await fs.access(outPath);
166
+ return;
167
+ } catch {
168
+ // continue
169
+ }
170
+
171
+ await fs.mkdir(folder, { recursive: true });
172
+ const payload = {
173
+ tool: 'apply_patch',
174
+ label,
175
+ reason,
176
+ capturedAt: new Date().toISOString(),
177
+ sourceFile,
178
+ argsSha256: crypto.createHash('sha256').update(originalArgs).digest('hex'),
179
+ originalArgs,
180
+ validationResult,
181
+ };
182
+ await fs.writeFile(outPath, JSON.stringify(payload, null, 2), 'utf-8');
183
+
184
+ captureState.totalCaptured += 1;
185
+ captureState.byReason.set(reason, currentReasonCount + 1);
186
+ }
187
+
114
188
  async function loadValidator() {
115
189
  if (!(await fileExists(coreLoaderPath))) {
116
190
  throw new Error(`core-loader missing at ${coreLoaderPath} (run npm run build:dev first)`);
@@ -123,7 +197,7 @@ async function loadValidator() {
123
197
  return { validateToolCall };
124
198
  }
125
199
 
126
- async function scanRoot(label, rootDir, validateToolCall) {
200
+ async function scanRoot(label, rootDir, validateToolCall, capture) {
127
201
  if (!(await fileExists(rootDir))) {
128
202
  return { label, rootDir, files: 0, calls: 0, ok: 0, byReason: new Map(), examples: [] };
129
203
  }
@@ -168,6 +242,17 @@ async function scanRoot(label, rootDir, validateToolCall) {
168
242
  const cur = byReason.get(reason) || { count: 0 };
169
243
  cur.count += 1;
170
244
  byReason.set(reason, cur);
245
+ if (capture?.enabled) {
246
+ await captureFailure({
247
+ destRoot: capture.destRoot,
248
+ label,
249
+ sourceFile: filePath,
250
+ reason,
251
+ originalArgs: args,
252
+ validationResult: res,
253
+ captureState: capture.state,
254
+ });
255
+ }
171
256
  if (examples.length < 12) {
172
257
  examples.push({ file: filePath, reason });
173
258
  }
@@ -200,17 +285,73 @@ function printReport(report) {
200
285
  async function main() {
201
286
  const { validateToolCall } = await loadValidator();
202
287
 
203
- const repo = await scanRoot('repo samples/ci-goldens', REPO_GOLDENS_ROOT, validateToolCall);
288
+ const captureEnabled = process.argv.slice(2).includes('--capture') || process.env.ROUTECODEX_SCAN_CAPTURE === '1';
289
+ const captureRepo = process.argv.slice(2).includes('--capture-repo') || process.env.ROUTECODEX_SCAN_CAPTURE_REPO === '1';
290
+ const captureRoot = process.env.ROUTECODEX_ERRORSAMPLES_DIR || ERROR_SAMPLES_ROOT;
291
+ const totalLimit = Number.parseInt(process.env.ROUTECODEX_CAPTURE_TOTAL_LIMIT || '', 10);
292
+ const perReasonLimit = Number.parseInt(process.env.ROUTECODEX_CAPTURE_PER_REASON_LIMIT || '', 10);
293
+ const reasonsArg = process.argv
294
+ .slice(2)
295
+ .find((arg) => arg.startsWith('--capture-reasons='))
296
+ ?.split('=')[1];
297
+ const reasonsEnv = process.env.ROUTECODEX_CAPTURE_REASONS;
298
+ const reasonAllowListRaw = (reasonsArg || reasonsEnv || '').trim();
299
+ const reasonAllowList = reasonAllowListRaw
300
+ ? new Set(
301
+ reasonAllowListRaw
302
+ .split(',')
303
+ .map((s) => s.trim())
304
+ .filter(Boolean)
305
+ )
306
+ : null;
307
+ const captureState = {
308
+ totalCaptured: 0,
309
+ totalLimit: Number.isFinite(totalLimit) ? totalLimit : DEFAULT_CAPTURE_TOTAL_LIMIT,
310
+ perReasonLimit: Number.isFinite(perReasonLimit) ? perReasonLimit : DEFAULT_CAPTURE_PER_REASON_LIMIT,
311
+ byReason: new Map(),
312
+ reasonAllowList,
313
+ };
314
+
315
+ const repo = await scanRoot('repo samples/ci-goldens', REPO_GOLDENS_ROOT, validateToolCall, {
316
+ enabled: captureEnabled && captureRepo,
317
+ destRoot: captureRoot,
318
+ state: captureState,
319
+ });
204
320
  printReport(repo);
205
321
 
206
322
  const userAll = process.argv.slice(2).includes('--user-all');
207
- const userRoot = userAll && (await fileExists(USER_CODEX_ROOT)) ? USER_CODEX_ROOT : (await fileExists(USER_CODEX_PENDING)) ? USER_CODEX_PENDING : USER_CODEX_ROOT;
208
- const user = await scanRoot(`user ${userAll ? '~/.routecodex/codex-samples (all)' : '~/.routecodex/codex-samples/openai-chat/__pending__'}`, userRoot, validateToolCall);
209
- printReport(user);
323
+ const userRoots = [];
324
+ if (userAll) {
325
+ if (await fileExists(USER_CODEX_ROOT)) userRoots.push({ label: 'user ~/.routecodex/codex-samples (all)', root: USER_CODEX_ROOT });
326
+ if (await fileExists(USER_CODEX_ROOT_ALT))
327
+ userRoots.push({ label: 'user ~/.routecodex/codex samples (all)', root: USER_CODEX_ROOT_ALT });
328
+ } else {
329
+ const pending = (await fileExists(USER_CODEX_PENDING)) ? USER_CODEX_PENDING : null;
330
+ if (pending) userRoots.push({ label: 'user ~/.routecodex/codex-samples/openai-chat/__pending__', root: pending });
331
+ else if (await fileExists(USER_CODEX_ROOT)) userRoots.push({ label: 'user ~/.routecodex/codex-samples', root: USER_CODEX_ROOT });
332
+ else if (await fileExists(USER_CODEX_ROOT_ALT)) userRoots.push({ label: 'user ~/.routecodex/codex samples', root: USER_CODEX_ROOT_ALT });
333
+ else userRoots.push({ label: 'user ~/.routecodex/codex-samples', root: USER_CODEX_ROOT });
334
+ }
210
335
 
211
- const totalCalls = repo.calls + user.calls;
212
- const totalOk = repo.ok + user.ok;
336
+ const userReports = [];
337
+ for (const entry of userRoots) {
338
+ const report = await scanRoot(entry.label, entry.root, validateToolCall, {
339
+ enabled: captureEnabled,
340
+ destRoot: captureRoot,
341
+ state: captureState,
342
+ });
343
+ userReports.push(report);
344
+ printReport(report);
345
+ }
346
+
347
+ const totalCalls = repo.calls + userReports.reduce((sum, r) => sum + r.calls, 0);
348
+ const totalOk = repo.ok + userReports.reduce((sum, r) => sum + r.ok, 0);
213
349
  console.log(`\n[scan] TOTAL apply_patch_calls=${totalCalls} ok=${totalOk} fail=${totalCalls - totalOk}`);
350
+ if (captureEnabled) {
351
+ console.log(
352
+ `[scan] captured_failures=${captureState.totalCaptured} dest=${captureRoot} (per_reason<=${captureState.perReasonLimit}, total<=${captureState.totalLimit})`
353
+ );
354
+ }
214
355
 
215
356
  process.exitCode = totalCalls - totalOk > 0 ? 1 : 0;
216
357
  }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ function runCase(args) {
6
+ const nodeArgs = ['scripts/unified-hub-shadow-compare.mjs', ...args];
7
+ const result = spawnSync(process.execPath, nodeArgs, {
8
+ cwd: path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'),
9
+ stdio: 'inherit'
10
+ });
11
+ if (result.status !== 0) {
12
+ throw new Error(`shadow compare failed: ${nodeArgs.join(' ')}`);
13
+ }
14
+ }
15
+
16
+ function main() {
17
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
18
+ const fixturesDir = path.join(repoRoot, 'tests', 'fixtures', 'unified-hub');
19
+
20
+ runCase([
21
+ '--request',
22
+ path.join(fixturesDir, 'responses.clean.json'),
23
+ '--entry-endpoint',
24
+ '/v1/responses',
25
+ '--route-hint',
26
+ 'responses',
27
+ '--baseline-mode',
28
+ 'off',
29
+ '--candidate-mode',
30
+ 'enforce'
31
+ ]);
32
+
33
+ console.log('[unified-hub-responses-enforce-safe] OK');
34
+ }
35
+
36
+ main();
37
+
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ function runCase(args) {
7
+ const nodeArgs = ['scripts/unified-hub-shadow-compare.mjs', ...args];
8
+ const result = spawnSync(process.execPath, nodeArgs, {
9
+ cwd: path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'),
10
+ stdio: 'inherit'
11
+ });
12
+ if (result.status !== 0) {
13
+ throw new Error(`shadow compare failed: ${nodeArgs.join(' ')}`);
14
+ }
15
+ }
16
+
17
+ function main() {
18
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
19
+ const fixturesDir = path.join(repoRoot, 'tests', 'fixtures', 'unified-hub');
20
+ if (!fs.existsSync(fixturesDir)) {
21
+ throw new Error(`fixtures dir missing: ${fixturesDir}`);
22
+ }
23
+
24
+ runCase([
25
+ '--request',
26
+ path.join(fixturesDir, 'chat.json'),
27
+ '--entry-endpoint',
28
+ '/v1/chat/completions',
29
+ '--route-hint',
30
+ 'openai'
31
+ ]);
32
+
33
+ runCase([
34
+ '--request',
35
+ path.join(fixturesDir, 'responses.json'),
36
+ '--entry-endpoint',
37
+ '/v1/responses',
38
+ '--route-hint',
39
+ 'responses'
40
+ ]);
41
+
42
+ runCase([
43
+ '--request',
44
+ path.join(fixturesDir, 'anthropic.json'),
45
+ '--entry-endpoint',
46
+ '/v1/messages',
47
+ '--route-hint',
48
+ 'anthropic'
49
+ ]);
50
+
51
+ console.log('[unified-hub-shadow-regression] OK');
52
+ }
53
+
54
+ main();
55
+