ruvnet-brain 4.5.4 → 4.5.5

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 (37) hide show
  1. package/README.md +2 -2
  2. package/bin/install.mjs +144 -27
  3. package/config/model-router/catalog.template.json +126 -52
  4. package/config/model-router/policy.default.mjs +94 -75
  5. package/config/model-router/qualification-contract.json +124 -0
  6. package/config/model-router/routing-eval-cases.json +275 -0
  7. package/config/model-router/routing-policy.template.json +76 -0
  8. package/config/model-router/weekly-analyst-instruction.md +60 -0
  9. package/data/model-catalog.json +44 -49
  10. package/package.json +3 -1
  11. package/plugin/.claude-plugin/plugin.json +1 -1
  12. package/plugin/.codex-plugin/plugin.json +1 -1
  13. package/plugin/scripts/codex-hook-adapter.mjs +18 -9
  14. package/scripts/codex-hook-trust-reconcile.mjs +247 -0
  15. package/scripts/codex-routed.sh +3 -36
  16. package/scripts/goldie-weekly.sh +8 -64
  17. package/scripts/metaharness-router.mjs +7 -1
  18. package/scripts/model-analyst-sandbox.mjs +54 -0
  19. package/scripts/model-currency-evidence.mjs +139 -0
  20. package/scripts/model-currency.mjs +230 -0
  21. package/scripts/model-native-catalog.mjs +111 -0
  22. package/scripts/model-native-qualification.mjs +251 -0
  23. package/scripts/model-router-agent-hook.mjs +136 -0
  24. package/scripts/model-router-dispatch.mjs +161 -0
  25. package/scripts/model-router-engine.mjs +155 -104
  26. package/scripts/model-routing-eval.mjs +108 -0
  27. package/scripts/model-routing-gateway.mjs +420 -0
  28. package/scripts/model-routing-launchers.mjs +174 -0
  29. package/scripts/model-routing-policy-promotion.mjs +203 -0
  30. package/scripts/model-weekly-analyst.mjs +299 -0
  31. package/scripts/model-weekly-assessment.mjs +91 -0
  32. package/scripts/model-weekly-cycle.mjs +183 -0
  33. package/scripts/model-weekly-qualification.mjs +362 -0
  34. package/scripts/native-subscription-usage.mjs +57 -0
  35. package/scripts/release-qualification-contract.mjs +41 -0
  36. package/scripts/security-guidance-codex-compat.mjs +142 -0
  37. package/scripts/user-model-prompt-hook.mjs +69 -0
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+ // DISTINCT-FROM: scripts/model-router-dispatch.mjs — same-session native JSONL proxy, never a worker.
3
+ // Manual host adapter: Codex cliExecutable / Claude claudeProcessWrapper. No settings are installed.
4
+ // New turns fail closed. Active Codex additions require an exact approved pair; Claude packets defer.
5
+ // Approvals, tools, cancellation and shutdown retain native protocols.
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import os from 'node:os';
9
+ import crypto from 'node:crypto';
10
+ import { StringDecoder } from 'node:string_decoder';
11
+ import { spawn, execFileSync } from 'node:child_process';
12
+ import { fileURLToPath, pathToFileURL } from 'node:url';
13
+ import { subscriptionEnvironment, assertSubscriptionAuth, validateDispatchDecision, loadNativeCodexModels } from './model-router-dispatch.mjs';
14
+
15
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
16
+ const REFUSED = 'Current reviewed native model/effort allocation unavailable; new turn was not forwarded.';
17
+ const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
18
+
19
+ /** Fresh policy-only subprocess: prompt stays on stdin, never argv, receipts or diagnostics. */
20
+ export function decideNativeTurn(prompt, harness, { env = process.env, spawnEngine = spawn, timeoutMs = 4000, multimodal = false } = {}) {
21
+ return new Promise((resolve, reject) => {
22
+ const child = spawnEngine(process.execPath, [env.MODEL_ROUTER_ENGINE || path.join(HERE, 'model-router-engine.mjs'),
23
+ '--harness', harness, '--policy-only', '--json', ...(multimodal ? ['--request-json'] : [])], { env, shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
24
+ let stdout = '', done = false;
25
+ const finish = (error, decision) => {
26
+ if (done) return;
27
+ done = true; clearTimeout(timer);
28
+ if (error) child.kill();
29
+ error ? reject(error) : resolve(decision);
30
+ };
31
+ const timer = setTimeout(() => finish(new Error(REFUSED)), timeoutMs);
32
+ child.stdout.on('data', (chunk) => { stdout += chunk; if (stdout.length > 131072) finish(new Error(REFUSED)); });
33
+ child.stderr.on('data', () => {});
34
+ child.once('error', () => finish(new Error(REFUSED)));
35
+ child.stdin.on('error', () => finish(new Error(REFUSED)));
36
+ child.once('exit', (code) => {
37
+ try { if (code !== 0) throw new Error(); finish(null, JSON.parse(stdout)); }
38
+ catch { finish(new Error(REFUSED)); }
39
+ });
40
+ child.stdin.end(multimodal ? JSON.stringify({ prompt: harness === 'claude-code' ? 'Unresolved architecture uncertainty: unclassified multimodal input' : prompt || 'Unclassified multimodal input', taskFacts: { uncertainty: 'architecture' } }) : prompt);
41
+ });
42
+ }
43
+
44
+ export function nativeGatewayLaunch({ harness, realBinary, args = [], env = process.env } = {}) {
45
+ if (!['codex', 'claude-code'].includes(harness) || !path.isAbsolute(realBinary || '')) {
46
+ throw new Error('Explicit absolute native host executable and supported harness required');
47
+ }
48
+ const binary = fs.realpathSync(realBinary);
49
+ if (binary === fs.realpathSync(fileURLToPath(import.meta.url)) || env.MODEL_ROUTER_GATEWAY_ACTIVE) {
50
+ throw new Error('Native routing gateway recursion refused');
51
+ }
52
+ const clean = subscriptionEnvironment(env);
53
+ clean.MODEL_ROUTER_GATEWAY_ACTIVE = '1';
54
+ const nativeArgs = [...args];
55
+ for (let index = 0; index < args.length; index++) {
56
+ const arg = args[index];
57
+ if (harness === 'codex' && (arg.startsWith('-c') || arg === '--config' || arg.startsWith('--config='))) {
58
+ const setting = arg.startsWith('--config=') ? arg.slice(9) : arg.startsWith('-c') && arg.length > 2 ? arg.slice(2).replace(/^=/, '') : args[++index];
59
+ const at = setting?.indexOf('=');
60
+ if (at == null || at < 0) throw new Error(REFUSED);
61
+ const key = setting.slice(0, at), raw = setting.slice(at + 1);
62
+ let value; try { value = JSON.parse(raw); } catch { value = raw; }
63
+ if (unsafeSettings({ [key]: value })) throw new Error(REFUSED);
64
+ }
65
+ if (harness === 'claude-code' && /^--settings(?:-file)?(?:=|$)/.test(arg)) {
66
+ const overlay = arg.includes('=') ? arg.slice(arg.indexOf('=') + 1) : args[++index];
67
+ let settings;
68
+ try { settings = JSON.parse(overlay.trim().startsWith('{') ? overlay : fs.readFileSync(overlay, 'utf8')); }
69
+ catch { throw new Error(REFUSED); }
70
+ if (unsafeSettings(settings)) throw new Error(REFUSED);
71
+ }
72
+ if (harness === 'codex' && /^(--profile(?:=|$)|-p)/.test(arg)) throw new Error(REFUSED);
73
+ if (/^--(?:betas|model-provider|api-key|base-url)(?:=|$)/.test(arg)) throw new Error(REFUSED);
74
+ }
75
+ if (harness === 'codex') {
76
+ if (!nativeArgs.includes('app-server')) throw new Error('Codex gateway requires native app-server mode');
77
+ nativeArgs.push('-c', 'model_provider="openai"', '-c', 'service_tier="default"', '-c', 'features.fast_mode=false');
78
+ } else if (!nativeArgs.includes('--input-format') || nativeArgs[nativeArgs.indexOf('--input-format') + 1] !== 'stream-json'
79
+ || !nativeArgs.includes('--output-format') || nativeArgs[nativeArgs.indexOf('--output-format') + 1] !== 'stream-json') {
80
+ throw new Error('Claude gateway requires bidirectional native stream-json mode');
81
+ }
82
+ return { command: binary, args: nativeArgs, env: clean };
83
+ }
84
+
85
+ export function appendGatewayReceipt(receipt, { env = process.env } = {}) {
86
+ const file = env.MODEL_ROUTER_GATEWAY_RECEIPTS || path.join(os.homedir(), '.claude', 'metaharness', 'native-turn-routing.jsonl');
87
+ fs.mkdirSync(path.dirname(file), { recursive: true });
88
+ const allowed = new Set(['ts', 'harness', 'status', 'model', 'effort', 'taskClass', 'modelObserved',
89
+ 'serviceMode', 'allowanceVerified', 'reservation', 'evidence', 'classificationSource']);
90
+ const metadata = Object.fromEntries(Object.entries(receipt).filter(([key]) => allowed.has(key)));
91
+ fs.appendFileSync(file, JSON.stringify(metadata) + '\n', { mode: 0o600 });
92
+ }
93
+
94
+ function promptFor(message, harness) {
95
+ const content = harness === 'codex' ? message.params?.input : message.message?.content;
96
+ if (typeof content === 'string') return { prompt: content, multimodal: false };
97
+ if (!Array.isArray(content)) throw new Error(REFUSED);
98
+ const text = content.filter((part) => part?.type === 'text').map((part) => part.text);
99
+ if (text.some((part) => typeof part !== 'string')) throw new Error(REFUSED);
100
+ const multimodal = content.some((part) => ['image', 'localImage', 'image_url'].includes(part?.type));
101
+ if (!text.length && !multimodal) throw new Error(REFUSED);
102
+ return { prompt: text.join('\n'), multimodal };
103
+ }
104
+
105
+ // Guard native provider/config overlays recursively, including flattened keys.
106
+ function unsafeSettings(value, prefix = '', routing = false) {
107
+ if (!value || typeof value !== 'object') return false;
108
+ return Object.entries(value).some(([key, entry]) => {
109
+ const name = prefix ? `${prefix}.${key}` : key;
110
+ if (/^(model_providers|apiKeyHelper|api_key|apiKey|base_url|baseUrl|auth_token|authToken|customHeaders|env|modelSettings|alwaysThinkingEnabled|maxEffortLevel)(\.|$)/.test(name)) return true;
111
+ if (/^(model_provider|modelProvider)$/.test(name) && entry !== 'openai') return true;
112
+ if (/^(service_tier|serviceTier)$/.test(name) && entry != null && entry !== 'default') return true;
113
+ if (/^(features\.fast_mode|fastMode)$/.test(name) && entry !== false) return true;
114
+ if (routing && /^(model|effort|effortLevel|reasoning_effort|collaborationMode)(\.|$)/.test(name)) return true;
115
+ return unsafeSettings(entry, name, routing);
116
+ });
117
+ }
118
+
119
+ export function verifyNativeVision(decision, models = loadNativeCodexModels()) {
120
+ const modalities = models.find((model) => model.slug === decision.model)?.input_modalities;
121
+ if (Array.isArray(modalities) && !modalities.includes('image')) throw new Error(REFUSED);
122
+ }
123
+
124
+ /** Pure native override: preserve every context, tool, approval and collaboration instruction. */
125
+ export function routeCodexTurn(message, decision) {
126
+ const params = { ...message.params, model: decision.model, effort: decision.effort, serviceTier: 'default' };
127
+ if (params.collaborationMode) params.collaborationMode = { ...params.collaborationMode,
128
+ settings: { ...params.collaborationMode.settings, model: decision.model, reasoning_effort: decision.effort } };
129
+ return { ...message, params };
130
+ }
131
+
132
+ /** Injectable protocol transport; no inference or model implementation lives in the gateway. */
133
+ export function connectNativeGateway({ harness, child, input, output, diagnostics = process.stderr,
134
+ decide = decideNativeTurn, verifyDecision = validateDispatchDecision, checkAuth = () => {},
135
+ receipt = appendGatewayReceipt, timeoutMs = 5000, maxDeferredBytes = 64 * 1024 * 1024, tempRoot = os.tmpdir(), now = () => new Date().toISOString() } = {}) {
136
+ const nonce = crypto.randomUUID();
137
+ const pending = new Map(), initIds = new Set(), ownIds = new Set(), held = new Set();
138
+ const accepted = new Map(), starts = new Map(), inactiveWaiters = new Set();
139
+ let serial = Promise.resolve(), ready = harness === 'codex', active = false, closed = false, serialNumber = 0;
140
+ let readiness = null, resolveReady;
141
+ let queuedBytes = 0, spoolDirectory = null, spoolSequence = 0, declineUsers = false;
142
+ const spoolKey = crypto.randomBytes(32);
143
+ const waitInactive = () => !active ? Promise.resolve() : new Promise((resolve) => inactiveWaiters.add(resolve));
144
+ const packetFor = (token) => {
145
+ if (token.packet) return token.packet;
146
+ const bytes = fs.readFileSync(token.file);
147
+ const decipher = crypto.createDecipheriv('aes-256-gcm', spoolKey, bytes.subarray(0, 12));
148
+ decipher.setAuthTag(bytes.subarray(12, 28));
149
+ const packet = JSON.parse(Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString('utf8'));
150
+ fs.unlinkSync(token.file); token.file = null; token.packet = packet; return packet;
151
+ };
152
+ const retire = (token) => {
153
+ if (!held.delete(token)) return;
154
+ queuedBytes -= token.bytes;
155
+ if (token.file) { fs.rmSync(token.file, { force: true }); token.file = null; }
156
+ };
157
+ const retain = (line, message) => {
158
+ const token = { bytes: Buffer.byteLength(line), cancelled: false };
159
+ if (harness !== 'claude-code' || held.size < 32) token.packet = message;
160
+ else {
161
+ if (!spoolDirectory) { spoolDirectory = fs.mkdtempSync(path.join(tempRoot, 'native-routing-')); fs.chmodSync(spoolDirectory, 0o700); }
162
+ const nonce = crypto.randomBytes(12), cipher = crypto.createCipheriv('aes-256-gcm', spoolKey, nonce);
163
+ const encrypted = Buffer.concat([cipher.update(line, 'utf8'), cipher.final()]);
164
+ token.file = path.join(spoolDirectory, `${++spoolSequence}.bin`);
165
+ fs.writeFileSync(token.file, Buffer.concat([nonce, cipher.getAuthTag(), encrypted]), { mode: 0o600 });
166
+ }
167
+ held.add(token); queuedBytes += token.bytes; return token;
168
+ };
169
+ // Honor writable backpressure while preserving order and pausing its upstream.
170
+ const writer = (destination, source) => {
171
+ const queue = [], idleWaiters = []; let blocked = false, stopped = false, resuming = false;
172
+ const settle = () => { if (!queue.length && !blocked) idleWaiters.splice(0).forEach((resolve) => resolve()); };
173
+ const flush = () => {
174
+ while (!stopped && !blocked && !resuming && queue.length) {
175
+ const entry = queue.shift();
176
+ if (entry.token?.cancelled) { entry.resolve(false); continue; }
177
+ blocked = !destination.write(entry.line);
178
+ if (entry.token) { entry.token.dispatched = true; retire(entry.token); if (harness === 'claude-code') active = true; }
179
+ entry.resolve(true);
180
+ }
181
+ if (blocked) source.pause(); else if (!stopped) source.resume();
182
+ settle();
183
+ };
184
+ destination.on('drain', () => {
185
+ blocked = false; resuming = true; if (!stopped) source.resume();
186
+ setImmediate(() => { resuming = false; flush(); });
187
+ });
188
+ const write = (value, token) => new Promise((resolve) => {
189
+ if (stopped) { resolve(false); return; }
190
+ queue.push({ line: typeof value === 'string' ? value + '\n' : JSON.stringify(value) + '\n', token, resolve }); flush();
191
+ });
192
+ write.idle = () => !queue.length && !blocked ? Promise.resolve() : new Promise((resolve) => idleWaiters.push(resolve));
193
+ write.close = () => { stopped = true; queue.splice(0).forEach((entry) => entry.resolve(false)); blocked = false; settle(); };
194
+ return write;
195
+ };
196
+ const hostWriter = writer(child.stdin, input), writeClient = writer(output, child.stdout);
197
+ const writeHost = (value, token) => { if (closed) throw new Error(REFUSED); return hostWriter(value, token); };
198
+ const record = (status, decision, observed = false, extra = {}) => receipt({ ts: now(), harness, status,
199
+ ...(decision ? { model: decision.model, effort: decision.effort, taskClass: decision.taskClass } : {}),
200
+ modelObserved: observed, ...(decision?.classificationSource ? { classificationSource: decision.classificationSource } : {}), ...extra });
201
+ const fail = (message, reason = REFUSED) => {
202
+ diagnostics.write(`[native-model-routing] ${reason}\n`);
203
+ if (harness === 'codex' && Object.hasOwn(message, 'id')) {
204
+ writeClient({ id: message.id, error: { code: -32001, message: reason } });
205
+ } else if (harness === 'claude-code' && message.type === 'control_request') {
206
+ writeClient({ type: 'control_response', response: { subtype: 'error', request_id: message.request_id, error: reason } });
207
+ } else if (harness === 'claude-code') {
208
+ writeClient({ type: 'result', subtype: 'error_during_execution', is_error: true,
209
+ errors: [reason], session_id: message.session_id || '', uuid: crypto.randomUUID(),
210
+ duration_ms: 0, duration_api_ms: 0, num_turns: 0, total_cost_usd: 0, usage: {}, modelUsage: {}, permission_denials: [] });
211
+ }
212
+ try { record('routing-refused'); } catch { /* diagnostics above are the fail-closed proof */ }
213
+ };
214
+ const waitReady = () => {
215
+ if (ready) return Promise.resolve();
216
+ if (!readiness) readiness = new Promise((resolve) => { resolveReady = resolve; });
217
+ return bounded(readiness);
218
+ };
219
+ function bounded(work) {
220
+ return new Promise((resolve, reject) => {
221
+ const timer = setTimeout(() => reject(new Error(REFUSED)), timeoutMs);
222
+ Promise.resolve(work).then((value) => { clearTimeout(timer); resolve(value); },
223
+ (error) => { clearTimeout(timer); reject(error); });
224
+ });
225
+ }
226
+ const control = (request) => new Promise((resolve, reject) => {
227
+ const id = `model-routing-gateway:${nonce}:${++serialNumber}`;
228
+ const timer = setTimeout(() => { pending.delete(id); reject(new Error(REFUSED)); }, timeoutMs);
229
+ ownIds.add(id);
230
+ pending.set(id, { resolve, reject, timer });
231
+ try {
232
+ writeHost(harness === 'codex' ? { id, ...request }
233
+ : { type: 'control_request', request_id: id, request });
234
+ } catch (error) { clearTimeout(timer); pending.delete(id); reject(error); }
235
+ });
236
+ async function route(token) {
237
+ let message;
238
+ const assertLive = () => { if (closed || token.cancelled) throw new Error('cancelled'); };
239
+ try {
240
+ message = packetFor(token);
241
+ await waitReady();
242
+ assertLive();
243
+ // Active Claude packets become unchanged next-turn input after the current result.
244
+ if (harness === 'claude-code' && active) {
245
+ try { record('active-input-deferred'); } catch { diagnostics.write('[native-model-routing] Deferred-input receipt unavailable; current work continues.\n'); }
246
+ await waitInactive(); assertLive();
247
+ }
248
+ const facts = promptFor(message, harness);
249
+ const decision = await bounded(decide(facts.prompt, harness, { multimodal: facts.multimodal }));
250
+ assertLive();
251
+ if (facts.multimodal && (decision.taskClass !== 'hard' || decision.effort !== 'high')) throw new Error(REFUSED);
252
+ if (facts.multimodal) decision.classificationSource = 'multimodal-uncertainty';
253
+ if (!decision?.subscriptionCovered || decision.harness !== harness || !EFFORTS.has(decision.effort)
254
+ || !/^[a-zA-Z0-9][a-zA-Z0-9._-]+$/.test(decision.model || '')) throw new Error(REFUSED);
255
+ await bounded(verifyDecision(decision));
256
+ if (facts.multimodal && harness === 'codex' && verifyDecision === validateDispatchDecision) {
257
+ verifyNativeVision(decision);
258
+ }
259
+ assertLive();
260
+ await bounded(checkAuth(harness));
261
+ assertLive();
262
+ if (harness === 'codex') {
263
+ if (message.method !== 'turn/start') {
264
+ const pair = accepted.get(message.params?.threadId);
265
+ if (!pair || pair.model !== decision.model || pair.effort !== decision.effort) {
266
+ fail(message, 'Input not submitted: requires a new turn with reviewed allocation; current work continues.'); return;
267
+ }
268
+ record('active-input-approved', decision, false, { evidence: 'native-turn-start.accepted-configured-pair' });
269
+ await writeHost(message, token); return;
270
+ }
271
+ const account = await control({ method: 'account/read', params: { refreshToken: false } });
272
+ assertLive();
273
+ if (account?.account?.type !== 'chatgpt') throw new Error(REFUSED);
274
+ const allowance = await control({ method: 'account/rateLimits/read', params: { excludeResetCreditDetails: true, supportsLunaReserve: false } });
275
+ assertLive();
276
+ if (allowance?.ordinaryUsageAllowed !== true) throw new Error(REFUSED);
277
+ record('turn-ready', decision, false, { serviceMode: 'standard', allowanceVerified: true, reservation: false });
278
+ starts.set(message.id, { threadId: message.params?.threadId, model: decision.model, effort: decision.effort });
279
+ if (await writeHost(routeCodexTurn(message, decision), token)) {
280
+ record('turn-forwarded', decision, false, { serviceMode: 'standard', allowanceVerified: true, reservation: false });
281
+ }
282
+ } else {
283
+ if (!['low', 'medium', 'high', 'xhigh'].includes(decision.effort)) throw new Error(REFUSED);
284
+ await control({ subtype: 'apply_flag_settings', settings: { model: decision.model, effortLevel: decision.effort } });
285
+ assertLive();
286
+ const settings = await control({ subtype: 'get_settings' });
287
+ assertLive();
288
+ if (settings?.applied?.model !== decision.model || settings?.applied?.effort !== decision.effort) throw new Error(REFUSED);
289
+ record('turn-ready', decision, true, { evidence: 'native-get_settings.applied' });
290
+ if (await writeHost(message, token)) record('turn-forwarded', decision, true, { evidence: 'native-get_settings.applied' });
291
+ }
292
+ } catch {
293
+ if (!message && !closed && !token.cancelled) diagnostics.write('[native-model-routing] Deferred transport integrity unavailable; packet not submitted; current work continues.\n');
294
+ if (!closed && !token.cancelled && !token.dispatched && message && !(harness === 'claude-code' && active)) fail(message);
295
+ else if (token.dispatched) diagnostics.write('[native-model-routing] Post-dispatch receipt unavailable; native work continues.\n');
296
+ }
297
+ finally { retire(token); }
298
+ }
299
+ const listenLines = (stream, handler) => {
300
+ let buffer = ''; const decoder = new StringDecoder('utf8');
301
+ const pump = () => {
302
+ let split;
303
+ while ((split = buffer.indexOf('\n')) !== -1) {
304
+ const line = buffer.slice(0, split); buffer = buffer.slice(split + 1);
305
+ handler(line);
306
+ }
307
+ };
308
+ stream.on('data', (chunk) => { buffer += decoder.write(chunk); pump(); });
309
+ stream.on('resume', () => { if (buffer) setImmediate(pump); });
310
+ stream.on('end', () => { buffer += decoder.end(); if (buffer) handler(buffer); });
311
+ };
312
+ listenLines(input, (line) => {
313
+ let message;
314
+ try { message = JSON.parse(line); } catch { if (!closed) writeHost(line); return; }
315
+ if (harness === 'claude-code' && message.type === 'control_request' && message.request?.subtype === 'initialize') initIds.add(message.request_id);
316
+ const routable = harness === 'codex' ? ['turn/start', 'turn/steer', 'thread/queue/add'].includes(message.method) : message.type === 'user';
317
+ if (routable && harness === 'claude-code' && (declineUsers || queuedBytes + Buffer.byteLength(line) > maxDeferredBytes)) {
318
+ declineUsers = true;
319
+ const reason = 'Deferred input capacity (64 MiB) exceeded; new user packet not submitted; current work continues.';
320
+ diagnostics.write(`[native-model-routing] ${reason}\n`);
321
+ try { record('deferred-capacity-refused'); } catch { /* keep parsing permission/cancel controls */ }
322
+ if (!active) fail(message, reason);
323
+ return;
324
+ }
325
+ const cancel = harness === 'codex' ? message.method === 'turn/interrupt' : message.request?.subtype === 'interrupt';
326
+ const shutdown = message.method === 'shutdown';
327
+ if (shutdown || (cancel && (harness === 'codex' || !active))) {
328
+ for (const token of held) {
329
+ if (!token.cancelled && (shutdown || harness !== 'codex' || !message.params?.threadId || message.params.threadId === token.packet?.params?.threadId)) {
330
+ token.cancelled = true; fail(packetFor(token), 'Native turn cancelled before dispatch.');
331
+ }
332
+ }
333
+ }
334
+ let unsafe = false;
335
+ if (harness === 'codex') {
336
+ const params = message.params || {};
337
+ unsafe = unsafeSettings(params.config) || unsafeSettings({ modelProvider: params.modelProvider ?? 'openai', serviceTier: params.serviceTier ?? 'default' });
338
+ if (message.method === 'account/login/start' && !['chatgpt', 'chatgptDeviceCode', 'chatgptAuthTokens'].includes(params.type)) unsafe = true;
339
+ if (message.method === 'thread/settings/update') unsafe ||= unsafeSettings(params, '', true);
340
+ const edits = message.method === 'config/value/write' ? [params] : message.method === 'config/batchWrite' ? params.edits || [] : [];
341
+ if (message.method === 'config/batchWrite' && params.reloadUserConfig === true) unsafe = true;
342
+ unsafe ||= edits.some((edit) => unsafeSettings({ [edit.keyPath]: edit.value }, '', true));
343
+ } else if (message.type === 'control_request') {
344
+ const request = message.request || {};
345
+ unsafe = ['set_model', 'set_max_thinking_tokens'].includes(request.subtype)
346
+ || (['apply_flag_settings', 'update_settings'].includes(request.subtype) && unsafeSettings(request.settings || request, '', true));
347
+ }
348
+ if (unsafe) { fail(message, 'Native allocation/provider override refused; reviewed gateway routing owns model and effort.'); return; }
349
+ if (routable) {
350
+ try { const token = retain(line, message); serial = serial.then(() => route(token)); }
351
+ catch { diagnostics.write('[native-model-routing] Deferred transport storage unavailable; packet not submitted; current work continues.\n'); if (!active) fail(message); }
352
+ }
353
+ else {
354
+ if (!closed) writeHost(line);
355
+ }
356
+ });
357
+ listenLines(child.stdout, (line) => {
358
+ let message;
359
+ try { message = JSON.parse(line); } catch { writeClient(line); return; }
360
+ const id = harness === 'codex' ? message.id : message.type === 'control_response' ? message.response?.request_id : null;
361
+ if (pending.has(id)) {
362
+ const waiter = pending.get(id); pending.delete(id); clearTimeout(waiter.timer);
363
+ const failed = harness === 'codex' ? message.error : message.response?.subtype !== 'success';
364
+ failed ? waiter.reject(new Error(REFUSED)) : waiter.resolve(harness === 'codex' ? message.result : message.response.response);
365
+ return;
366
+ }
367
+ if (starts.has(id)) {
368
+ const pair = starts.get(id); starts.delete(id);
369
+ if (!message.error && message.result?.turn) accepted.set(pair.threadId, pair);
370
+ }
371
+ if (message.method === 'turn/completed') accepted.delete(message.params?.threadId);
372
+ if (ownIds.has(id)) return; // late own-control replies never escape into the host client
373
+ if (initIds.has(id) && message.response?.subtype === 'success') {
374
+ ready = true; active = message.response.response?.session_state && message.response.response.session_state !== 'idle'; resolveReady?.();
375
+ }
376
+ if (harness === 'claude-code' && ['assistant', 'stream_event'].includes(message.type)) active = true;
377
+ if (harness === 'claude-code' && message.type === 'result') { active = false; inactiveWaiters.forEach((resolve) => resolve()); inactiveWaiters.clear(); }
378
+ writeClient(line);
379
+ });
380
+ child.stderr?.on('data', (chunk) => diagnostics.write(chunk));
381
+ const close = () => {
382
+ closed = true; hostWriter.close(); inactiveWaiters.forEach((resolve) => resolve()); inactiveWaiters.clear();
383
+ for (const token of [...held]) { token.cancelled = true; retire(token); }
384
+ if (spoolDirectory) fs.rmSync(spoolDirectory, { recursive: true, force: true });
385
+ spoolKey.fill(0);
386
+ for (const waiter of pending.values()) { clearTimeout(waiter.timer); waiter.reject(new Error(REFUSED)); }
387
+ pending.clear();
388
+ };
389
+ child.once('error', close); child.once('exit', close); child.stdin.on('error', close); child.stdout.on('error', close);
390
+ input.on('end', () => { serial.finally(async () => { await hostWriter.idle(); if (!closed) child.stdin.end(); }); });
391
+ return { idle: () => serial, close, spoolState: () => ({ directory: spoolDirectory, queuedBytes }) };
392
+ }
393
+
394
+ export function parseGatewayInvocation(argv, env = process.env) {
395
+ const args = [...argv];
396
+ let harness, realBinary;
397
+ if (args[0] === '--harness') { harness = args[1]; args.splice(0, 2); }
398
+ if (['--real-binary', '--executable'].includes(args[0])) { realBinary = args[1]; args.splice(0, 2); }
399
+ if (args[0] === '--') args.shift();
400
+ if (!harness) harness = args.includes('app-server') ? 'codex' : 'claude-code';
401
+ if (!realBinary) realBinary = harness === 'codex' ? env.MODEL_ROUTER_REAL_CODEX : args.shift();
402
+ return { harness, realBinary, args, env };
403
+ }
404
+
405
+ async function main(argv) {
406
+ const launch = nativeGatewayLaunch(parseGatewayInvocation(argv));
407
+ const harness = parseGatewayInvocation(argv).harness;
408
+ const auth = (host) => assertSubscriptionAuth(host, { env: launch.env,
409
+ probe: (_command, args, options) => execFileSync(launch.command, args, options) });
410
+ auth(harness);
411
+ const child = spawn(launch.command, launch.args, { env: launch.env, cwd: process.cwd(), shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
412
+ const gateway = connectNativeGateway({ harness, child, input: process.stdin, output: process.stdout,
413
+ decide: (prompt, host, metadata) => decideNativeTurn(prompt, host, { env: launch.env, ...metadata }), checkAuth: auth });
414
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => { gateway.close(); child.kill(signal); });
415
+ child.once('exit', (code, signal) => { process.stdin.pause(); process.stdin.unref?.(); process.exitCode = code ?? (signal ? 1 : 0); });
416
+ child.once('error', () => { process.stderr.write('[native-model-routing] Native host transport unavailable.\n'); process.exitCode = 1; });
417
+ }
418
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
419
+ main(process.argv.slice(2)).catch(() => { process.stderr.write('[native-model-routing] Explicit native executable or subscription authentication unavailable.\n'); process.exitCode = 1; });
420
+ }
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ // Permanent POSIX native launchers (macOS/Linux). Windows shell launchers are unsupported.
3
+ // Explicit installation only; no login/Keychain changes.
4
+ // VS Code Codex APPLICATION settings belong on the UI client; Claude MACHINE settings
5
+ // belong on the extension host. A saved setting is not proof an existing process reloaded.
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import os from 'node:os';
9
+ import crypto from 'node:crypto';
10
+ import { spawn } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const SELF = fileURLToPath(import.meta.url);
14
+ const sha = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
15
+ const shellQuote = (value) => `'${String(value).replaceAll("'", "'\\''")}'`;
16
+ const KEYS = { codex: 'chatgpt.cliExecutable', 'claude-code': 'claudeCode.claudeProcessWrapper' };
17
+
18
+ function regular(file) {
19
+ const stat = fs.lstatSync(file);
20
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Regular file required: ${file}`);
21
+ return fs.realpathSync(file);
22
+ }
23
+
24
+ export function discoverNativeCodex(extensionsRoot) {
25
+ const manifest = JSON.parse(fs.readFileSync(path.join(extensionsRoot, 'extensions.json'), 'utf8'));
26
+ const entries = manifest.filter((entry) => entry.identifier?.id === 'openai.chatgpt');
27
+ if (entries.length !== 1) throw new Error('Exactly one registered Codex extension required');
28
+ const extension = fs.realpathSync(entries[0].location.path);
29
+ const root = fs.realpathSync(extensionsRoot);
30
+ if (!extension.startsWith(`${root}${path.sep}`)) throw new Error('Registered extension escaped extension root');
31
+ const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : process.platform;
32
+ const arch = process.arch === 'arm64' ? 'aarch64' : process.arch === 'x64' ? 'x86_64' : process.arch;
33
+ const binary = regular(path.join(extension, 'bin', `${platform}-${arch}`, process.platform === 'win32' ? 'codex.exe' : 'codex'));
34
+ fs.accessSync(binary, fs.constants.X_OK);
35
+ return binary;
36
+ }
37
+
38
+ export function launcherInvocation({ harness, args, config, launcherPath }) {
39
+ let nativeArgs = [...args]; let binary;
40
+ if (harness === 'codex') binary = discoverNativeCodex(config.extensionsRoot);
41
+ else if (harness === 'claude-code') {
42
+ if (!path.isAbsolute(nativeArgs[0] || '')) throw new Error('Claude wrapper requires the native binary as its first argument');
43
+ binary = regular(nativeArgs.shift());
44
+ const root = fs.realpathSync(config.extensionsRoot);
45
+ if (!binary.startsWith(`${root}${path.sep}`) || path.basename(binary) !== (process.platform === 'win32' ? 'claude.exe' : 'claude')) throw new Error('Claude wrapper binary is not an extension native executable');
46
+ } else throw new Error('Unsupported native host');
47
+ if (binary === fs.realpathSync(launcherPath) || binary === fs.realpathSync(SELF)) throw new Error('Native launcher recursion refused');
48
+ // These extension probes/transport bridges do not submit model turns.
49
+ const probe = nativeArgs.length === 1 && ['--version', '--help'].includes(nativeArgs[0]);
50
+ const bridge = harness === 'codex' && nativeArgs.length === 2 && nativeArgs[0] === 'stdio-to-uds';
51
+ if (probe || bridge) return { command: binary, args: nativeArgs, routed: false };
52
+ regular(config.gatewayPath);
53
+ return { command: config.nodeBinary, args: [config.gatewayPath, '--harness', harness, '--real-binary', binary, '--', ...nativeArgs], routed: true };
54
+ }
55
+
56
+ function atomic(file, bytes, mode = 0o600) {
57
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
58
+ if (fs.existsSync(file)) regular(file);
59
+ const temp = `${file}.${crypto.randomUUID()}.tmp`;
60
+ const fd = fs.openSync(temp, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, mode);
61
+ try { fs.writeFileSync(fd, bytes); fs.fchmodSync(fd, mode); fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
62
+ try { fs.renameSync(temp, file); } finally { if (fs.existsSync(temp)) fs.unlinkSync(temp); }
63
+ // Windows cannot fsync a directory handle through Node. The file was flushed
64
+ // above and replacement remains atomic; POSIX additionally flushes the directory.
65
+ if (process.platform === 'win32') return;
66
+ const dirFd = fs.openSync(path.dirname(file), fs.constants.O_RDONLY);
67
+ try { fs.fsyncSync(dirFd); } finally { fs.closeSync(dirFd); }
68
+ }
69
+
70
+ function backup(file) {
71
+ if (!fs.existsSync(file)) return null;
72
+ regular(file);
73
+ const bytes = fs.readFileSync(file); const target = `${file}.${sha(bytes)}.original`;
74
+ if (fs.existsSync(target)) { if (sha(fs.readFileSync(regular(target))) !== sha(bytes)) throw new Error('Backup digest mismatch'); }
75
+ else { const fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o400);
76
+ try { fs.writeFileSync(fd, bytes); fs.fsyncSync(fd); } finally { fs.closeSync(fd); } }
77
+ return target;
78
+ }
79
+
80
+ /** Collect native gateway, policy engine and their literal local dependencies into an immutable snapshot. */
81
+ export function runtimeSnapshot(sourceRoot) {
82
+ const root = fs.realpathSync(sourceRoot); const files = new Map();
83
+ const visit = (relative) => {
84
+ if (files.has(relative)) return;
85
+ const file = regular(path.join(root, relative));
86
+ if (!file.startsWith(`${root}${path.sep}`)) throw new Error('Runtime dependency escaped source root');
87
+ const bytes = fs.readFileSync(file); files.set(relative, bytes);
88
+ for (const match of bytes.toString().matchAll(/(?:from\s*|import\(\s*)['"](\.[^'"]+)['"]/g)) {
89
+ visit(path.relative(root, path.resolve(path.dirname(file), match[1])));
90
+ }
91
+ };
92
+ for (const relative of ['scripts/model-routing-gateway.mjs', 'scripts/model-router-engine.mjs', 'scripts/metaharness-router.mjs']) visit(relative);
93
+ for (const name of fs.readdirSync(path.join(root, 'config/model-router'))) {
94
+ if (/\.(?:json|mjs)$/.test(name)) visit(`config/model-router/${name}`);
95
+ }
96
+ const identity = [...files].sort(([a], [b]) => a.localeCompare(b)).map(([file, bytes]) => [file, sha(bytes)]);
97
+ return { files, identity, digest: sha(JSON.stringify(identity)) };
98
+ }
99
+
100
+ function stableNode() {
101
+ for (const file of ['/opt/homebrew/bin/node', process.execPath]) {
102
+ try { if (fs.realpathSync(file) === fs.realpathSync(process.execPath)) return file; } catch {}
103
+ }
104
+ return process.execPath;
105
+ }
106
+
107
+ export function installNativeLaunchers({ sourceRoot, home = os.homedir(), extensionsRoot = path.join(home, '.vscode-server/extensions'), nodeBinary = stableNode(), apply = false } = {}) {
108
+ // VS Code spawns its executable directly. A .cmd/.sh surrogate is not a proved
109
+ // Windows executable adapter; refuse before writing a misleading registration.
110
+ if (!['darwin', 'linux'].includes(process.platform)) throw new Error('Permanent native routing launchers support macOS/Linux only; Windows executable adapter is not implemented');
111
+ const snapshot = runtimeSnapshot(sourceRoot);
112
+ const base = path.join(home, '.cache/ruvnet-brain/model-routing');
113
+ const runtime = path.join(base, 'versions', snapshot.digest);
114
+ const runner = path.join(base, 'model-routing-launchers.mjs');
115
+ const configPath = path.join(base, 'launcher-config.json');
116
+ const config = { extensionsRoot: fs.realpathSync(extensionsRoot), nodeBinary: (regular(fs.realpathSync(nodeBinary)), nodeBinary), gatewayPath: path.join(runtime, 'scripts/model-routing-gateway.mjs'), runtimeDigest: snapshot.digest };
117
+ const codexBinary = discoverNativeCodex(config.extensionsRoot);
118
+ const launchers = Object.fromEntries(Object.keys(KEYS).map((host) => [host, path.join(home, '.local/bin', `ruvnet-brain-${host === 'codex' ? 'codex' : 'claude'}-gateway`)]));
119
+ const texts = Object.fromEntries(Object.entries(launchers).map(([host]) => [host, `#!/bin/sh\n# Managed RuvNet Brain native routing launcher\nexec ${shellQuote(config.nodeBinary)} ${shellQuote(runner)} --launch ${shellQuote(host)} --config ${shellQuote(configPath)} --launcher "$0" -- "$@"\n`]));
120
+ for (const [host, file] of Object.entries(launchers)) {
121
+ if (fs.existsSync(file) && !fs.readFileSync(regular(file), 'utf8').includes('# Managed RuvNet Brain native routing launcher')) throw new Error(`Preserving unmanaged launcher: ${file}`);
122
+ }
123
+ const receipt = { apply, launchers, config, codexBinary, settings: Object.fromEntries(Object.entries(KEYS).map(([host, key]) => [key, launchers[host]])), runtimeFiles: snapshot.identity };
124
+ if (!apply) return receipt;
125
+ for (const [relative, bytes] of snapshot.files) {
126
+ const file = path.join(runtime, relative);
127
+ if (fs.existsSync(file)) { if (sha(fs.readFileSync(regular(file))) !== sha(bytes)) throw new Error('Immutable runtime snapshot mismatch'); }
128
+ else atomic(file, bytes);
129
+ }
130
+ for (const file of [runner, configPath, ...Object.values(launchers)]) backup(file);
131
+ atomic(runner, fs.readFileSync(SELF)); atomic(configPath, `${JSON.stringify(config, null, 2)}\n`);
132
+ for (const [host, file] of Object.entries(launchers)) atomic(file, texts[host], 0o755);
133
+ return receipt;
134
+ }
135
+
136
+ /** Apply only absent or identical settings. Conflicting user overrides are preserved and disclosed. */
137
+ export function updateLauncherSettings(file, requested, { apply = false } = {}) {
138
+ const text = fs.existsSync(file) ? fs.readFileSync(regular(file), 'utf8') : '{}\n';
139
+ let settings;
140
+ try { settings = JSON.parse(text); } catch { throw new Error('Settings are JSONC or malformed; refusing a destructive rewrite'); }
141
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) throw new Error('Settings object required');
142
+ const add = {}; const preserved = {};
143
+ for (const [key, value] of Object.entries(requested)) {
144
+ if (!Object.values(KEYS).includes(key)) throw new Error('Unsupported launcher setting');
145
+ if (settings[key] === undefined || settings[key] === null || settings[key] === '') add[key] = value;
146
+ else if (settings[key] !== value) preserved[key] = settings[key];
147
+ }
148
+ Object.assign(settings, add);
149
+ const changed = Object.keys(add).length > 0;
150
+ const receipt = { file, apply, changed, preservedOverrides: preserved, settings: requested, backup: null };
151
+ if (apply && changed) { receipt.backup = backup(file); atomic(file, `${JSON.stringify(settings, null, 2)}\n`, fs.existsSync(file) ? fs.statSync(file).mode & 0o777 : 0o600); }
152
+ return receipt;
153
+ }
154
+
155
+ if (process.argv[1] && path.resolve(process.argv[1]) === SELF) {
156
+ const argv = process.argv.slice(2);
157
+ if (argv[0] === '--launch') {
158
+ try {
159
+ const harness = argv[1]; const configPath = argv[3]; const launcherPath = argv[5];
160
+ if (argv[2] !== '--config' || argv[4] !== '--launcher' || argv[6] !== '--') throw new Error('Invalid launcher arguments');
161
+ const invocation = launcherInvocation({ harness, args: argv.slice(7), config: JSON.parse(fs.readFileSync(regular(configPath))), launcherPath });
162
+ const child = spawn(invocation.command, invocation.args, { stdio: 'inherit', shell: false, env: process.env });
163
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(signal, () => child.kill(signal));
164
+ child.once('error', () => { process.stderr.write('Native routing launcher unavailable\n'); process.exitCode = 1; });
165
+ child.once('exit', (code) => { process.exitCode = code ?? 1; });
166
+ } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
167
+ } else {
168
+ try {
169
+ const apply = argv.includes('--apply'); const sourceAt = argv.indexOf('--source-root');
170
+ if (sourceAt < 0 || !argv[sourceAt + 1] || argv.some((arg, i) => !['--apply', '--source-root'].includes(arg) && i !== sourceAt + 1)) throw new Error('Usage: model-routing-launchers.mjs --source-root ABSOLUTE_PATH [--apply]');
171
+ console.log(JSON.stringify(installNativeLaunchers({ sourceRoot: argv[sourceAt + 1], apply }), null, 2));
172
+ } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
173
+ }
174
+ }