ruvnet-brain 4.5.10 → 4.5.12

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.
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ const assertModelRoutingText = async text => (await import('./model-routing-defence.mjs')).assertModelRoutingText(text);
3
+ // A controlled prompt boundary using native print/SDK controls, not the native terminal UI.
4
+ import path from 'node:path';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import crypto from 'node:crypto';
8
+ import { spawn, execFileSync } from 'node:child_process';
9
+ import { createManagedTerminal } from './managed-terminal-input.mjs';
10
+ import { pathToFileURL } from 'node:url';
11
+ import { StringDecoder } from 'node:string_decoder';
12
+ import { decideNativeTurn, appendGatewayReceipt } from './model-routing-gateway.mjs';
13
+ import { subscriptionEnvironment, assertSubscriptionAuth, validateDispatchDecision } from './model-router-dispatch.mjs';
14
+ import { validateClaudeTerminalSettings } from './model-terminal-launchers.mjs';
15
+ import { runManagedPrompt } from './model-managed-prompt.mjs';
16
+
17
+ const REFUSED = 'Controlled Claude turn refused; no fallback.';
18
+ const uuid = value => /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(value || '');
19
+ const cleanText = value => String(value).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, '');
20
+ const allocationEnv = /^(CLAUDE_CODE_EXTRA_BODY|CLAUDE_CODE_EFFORT_LEVEL|ANTHROPIC_DEFAULT_.*_MODEL|ANTHROPIC_MODEL|ANTHROPIC_SMALL_FAST_MODEL|CLAUDE_CODE_SUBAGENT_MODEL|CLAUDE_CODE_PLUGIN_(DIRS|CACHE_DIR|SEED_DIR)|CLAUDE_CODE_USE_COWORK_PLUGINS)$/;
21
+
22
+ /** Classic Brain hooks/MCP/skills remain loaded; native request-rewriting modules are refused. */
23
+ export function assertClaudeModuleBoundary(settings, { env = process.env, sessionId, read = fs.readFileSync } = {}) {
24
+ if (!settings || typeof settings !== 'object' || Object.keys(settings.env || {}).some(key => allocationEnv.test(key))) throw new Error(REFUSED);
25
+ const modules = value => value && typeof value === 'object' && Object.entries(value).some(([key, entry]) =>
26
+ key === 'modules' && (Array.isArray(entry) ? entry.length > 0 : Boolean(entry)) || modules(entry));
27
+ if (modules(settings.hooks)) throw new Error(REFUSED);
28
+ const config = env.CLAUDE_CONFIG_DIR || path.join(env.HOME || os.homedir(), '.claude');
29
+ const json = file => JSON.parse(read(file, 'utf8'));
30
+ const enabled = Object.entries(settings.enabledPlugins || {}).filter(([, value]) => value !== false).map(([key]) => key);
31
+ let registry;
32
+ if (enabled.length) registry = json(path.join(config, 'plugins', 'installed_plugins.json'));
33
+ for (const id of enabled) {
34
+ const installations = registry.plugins?.[id];
35
+ if (!Array.isArray(installations) || !installations.length) throw new Error(REFUSED);
36
+ const at = id.lastIndexOf('@'), name = id.slice(0, at), marketplace = id.slice(at + 1);
37
+ if (at < 1) throw new Error(REFUSED);
38
+ const known = json(path.join(config, 'plugins', 'known_marketplaces.json'));
39
+ const marketplaceRoot = known[marketplace]?.installLocation;
40
+ if (!path.isAbsolute(marketplaceRoot || '')) throw new Error(REFUSED);
41
+ const entry = json(path.join(marketplaceRoot, '.claude-plugin', 'marketplace.json')).plugins?.find(plugin => plugin.name === name);
42
+ if (!entry || modules(entry)) throw new Error(REFUSED);
43
+ // Refuse any registered module-bearing installation of an enabled ID; never guess scope precedence.
44
+ for (const installation of installations) {
45
+ const root = installation.installPath;
46
+ if (!path.isAbsolute(root || '')) throw new Error(REFUSED);
47
+ const manifestFiles = [path.join(root, '.claude-plugin', 'plugin.json'), path.join(root, 'plugin.json')];
48
+ // Native strict:false marketplace entries may supply the entire manifest (e.g. LSP plugins).
49
+ const manifests = manifestFiles.filter(file => fs.existsSync(file)).map(json);
50
+ if (!manifests.length && entry.strict === false) manifests.push(entry);
51
+ if (!manifests.length || manifests.some(modules)) throw new Error(REFUSED);
52
+ const hooks = [path.join(root, 'hooks', 'hooks.json')];
53
+ for (const relative of [...manifests.map(manifest => manifest.hooks), entry.hooks].flat().filter(value => typeof value === 'string')) {
54
+ const resolved = path.resolve(root, relative);
55
+ if (!resolved.startsWith(root + path.sep)) throw new Error(REFUSED);
56
+ hooks.push(resolved);
57
+ }
58
+ for (const file of hooks) if (fs.existsSync(file) && modules(json(file))) throw new Error(REFUSED);
59
+ }
60
+ }
61
+ const nonempty = folder => fs.existsSync(folder) && fs.readdirSync(folder).length > 0;
62
+ // Native 2.1.289 rft()/b(sessionId): <config-dir>/dev-mods/<session-id>.
63
+ if (nonempty(path.join(config, 'dev-mods', sessionId))) throw new Error(REFUSED);
64
+ const projects = path.join(config, 'projects');
65
+ if (fs.existsSync(projects)) for (const directory of fs.readdirSync(projects)) {
66
+ const transcript = path.join(projects, directory, `${sessionId}.jsonl`);
67
+ if (!fs.existsSync(transcript)) continue;
68
+ // A resumed transcript may restore a different dev-mods folder (native last-wins record).
69
+ for (const line of read(transcript, 'utf8').split('\n').filter(Boolean)) {
70
+ const record = JSON.parse(line);
71
+ if (record.type === 'dev-mods' && record.folder && nonempty(record.folder)) throw new Error(REFUSED);
72
+ }
73
+ }
74
+ }
75
+
76
+ /** Retire only the directly owned process; never infer descendant cleanup from its exit. */
77
+ export function retireControlledClaudeChild(child, { graceMs = 200, killMs = 200 } = {}) {
78
+ return new Promise(resolve => {
79
+ let finished = false, escalation, deadline;
80
+ const finish = () => {
81
+ if (finished) return;
82
+ finished = true; clearTimeout(escalation); clearTimeout(deadline);
83
+ child.removeListener('close', finish);
84
+ for (const stream of [child.stdin, child.stdout, child.stderr]) { stream?.removeAllListeners('data'); stream?.destroy(); }
85
+ child.unref?.(); resolve();
86
+ };
87
+ child.once('close', finish);
88
+ const kill = signal => { try { child.kill(signal); } catch { /* bounded cleanup still proceeds */ } };
89
+ escalation = setTimeout(() => kill('SIGKILL'), graceMs);
90
+ deadline = setTimeout(finish, graceMs + killMs);
91
+ kill('SIGTERM');
92
+ if (child.exitCode != null || child.signalCode != null) finish();
93
+ });
94
+ }
95
+
96
+ export function controlledClaudeArguments(decision, sessionId, resume = false, responseSchema) {
97
+ if (decision?.harness !== 'claude-code' || decision.provider !== 'anthropic' || decision.subscriptionCovered !== true ||
98
+ !/^claude-[a-z0-9][a-z0-9.-]*$/.test(decision.model || '') ||
99
+ !['low', 'medium', 'high', 'xhigh', 'max'].includes(decision.effort) || !uuid(sessionId)) throw new Error(REFUSED);
100
+ return [...(responseSchema ? ['--json-schema', JSON.stringify(responseSchema)] : []), '--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
101
+ '--model', decision.model, '--effort', decision.effort, '--permission-mode', 'manual', '--permission-prompts', 'host',
102
+ '--permission-prompt-tool', 'stdio',
103
+ '--disallowedTools', 'Agent,Task',
104
+ resume ? '--resume' : '--session-id', sessionId];
105
+ }
106
+
107
+ /** One native process per routed turn. Permission requests require an explicit host answer. */
108
+ export async function runControlledClaudeTurn({ binary, prompt, decisionPrompt = prompt, sessionId = crypto.randomUUID(), resume = false,
109
+ cwd = process.cwd(), env = process.env, decide = decideNativeTurn, verifyDecision = validateDispatchDecision,
110
+ checkSettings = validateClaudeTerminalSettings, checkAuth = assertSubscriptionAuth, spawnNative = spawn,
111
+ probe = execFileSync, approve = async () => false, output = () => {}, receipt = appendGatewayReceipt,
112
+ checkModules = assertClaudeModuleBoundary, responseSchema, validateStructuredOutput, signal, timeoutMs = 900000, handshakeMs = 10000 } = {}) {
113
+ if (!path.isAbsolute(binary || '') || typeof prompt !== 'string' || !prompt.trim() || prompt.length > 200000 ||
114
+ /^\s*\//.test(prompt) || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 900000 ||
115
+ !Number.isSafeInteger(handshakeMs) || handshakeMs <= 0 || handshakeMs > 30000 || signal?.aborted) throw new Error(REFUSED);
116
+ if (responseSchema && (responseSchema.type !== 'object' || typeof validateStructuredOutput !== 'function'
117
+ || Buffer.byteLength(JSON.stringify(responseSchema)) > 65536)) throw new Error(REFUSED);
118
+ const limit = performance.now() + timeoutMs;
119
+ const expired = () => signal?.aborted || performance.now() >= limit;
120
+ await assertModelRoutingText(prompt);
121
+ if (expired()) throw new Error(REFUSED);
122
+ // Extra body / effort env overrides can bypass the native CLI's requested allocation.
123
+ if (Object.keys(env).some(key => allocationEnv.test(key))) throw new Error(REFUSED);
124
+ const clean = subscriptionEnvironment(env);
125
+ const managed = process.platform === 'darwin' ? '/Library/Application Support/ClaudeCode/managed-settings.json' : '/etc/claude-code/managed-settings.json';
126
+ if (fs.existsSync(managed)) throw new Error('Managed Claude settings require separate module-boundary qualification.');
127
+ checkSettings({ env: clean, cwd, home: env.HOME || os.homedir() });
128
+ checkAuth('claude-code', { env: clean, probe: (_name, args, options) => probe(binary, args, options) });
129
+ const decision = await decide(decisionPrompt, 'claude-code', { env: clean });
130
+ verifyDecision(decision);
131
+ if (expired()) throw new Error(REFUSED);
132
+ const args = controlledClaudeArguments(decision, sessionId, resume, responseSchema);
133
+ if (expired()) throw new Error(REFUSED);
134
+ const child = spawnNative(binary, args, { cwd, env: clean, shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
135
+ return new Promise((resolve, reject) => {
136
+ let done = false, phase = 'initialize', buffer = '', bytes = 0, result, observed = false, permissions = false;
137
+ const assistantText = [];
138
+ let structuredAnswer;
139
+ const decoder = new StringDecoder('utf8');
140
+ const ids = { initialize: crypto.randomUUID(), before: crypto.randomUUID(), after: crypto.randomUUID() };
141
+ let handshake;
142
+ const timer = setTimeout(() => fail(), Math.max(1, limit - performance.now()));
143
+ const abort = () => fail();
144
+ signal?.addEventListener('abort', abort, { once: true });
145
+ const clear = () => { clearTimeout(timer); clearTimeout(handshake); signal?.removeEventListener('abort', abort); };
146
+ const fail = () => {
147
+ if (done) return;
148
+ done = true; clear();
149
+ void retireControlledClaudeChild(child).then(() => reject(new Error(REFUSED)));
150
+ };
151
+ const send = message => { if (done) return; if (expired()) return fail(); child.stdin.write(JSON.stringify(message) + '\n'); };
152
+ const control = which => {
153
+ phase = which; clearTimeout(handshake); handshake = setTimeout(fail, handshakeMs);
154
+ send({ type: 'control_request', request_id: ids[which], request: { subtype: which === 'initialize' ? 'initialize' : 'get_settings' } });
155
+ };
156
+ const settingsMatch = value => value?.applied?.model === decision.model && value.applied.effort === decision.effort && !value.errors?.length &&
157
+ Array.isArray(value.sources) && !value.sources.some(source => source.source === 'policySettings' && Object.keys(source.settings || {}).length);
158
+ const onMessage = async message => {
159
+ if (done) return;
160
+ if (expired()) return fail();
161
+ if (message.type === 'control_response') {
162
+ const response = message.response;
163
+ if (response?.request_id !== ids[phase] || response.subtype !== 'success') return fail();
164
+ if (phase === 'initialize') return control('before');
165
+ if (!settingsMatch(response.response)) return fail();
166
+ if (phase === 'before') {
167
+ clearTimeout(handshake); phase = 'turn';
168
+ checkModules(response.response.effective, { env: clean, sessionId });
169
+ verifyDecision(decision); // Allocation may have changed while native startup was running.
170
+ receipt({ ts: new Date().toISOString(), harness: 'claude-code', status: 'native-settings-observed',
171
+ model: decision.model, effort: decision.effort, taskClass: decision.taskClass, modelObserved: false,
172
+ evidence: 'get_settings applied model and effort before prompt; execution identity pending' }, { env: clean });
173
+ send({ type: 'user', session_id: sessionId, message: { role: 'user', content: prompt } });
174
+ } else if (phase === 'after') { checkModules(response.response.effective, { env: clean, sessionId }); phase = 'exit'; clearTimeout(handshake); child.stdin.end(); }
175
+ else fail();
176
+ return;
177
+ }
178
+ if (message.type === 'control_request') {
179
+ if (phase !== 'turn' || permissions || message.request?.subtype !== 'can_use_tool' || typeof message.request_id !== 'string') return fail();
180
+ if (['Agent', 'Task'].includes(message.request.tool_name)) return fail();
181
+ permissions = true;
182
+ let allowed = false;
183
+ try {
184
+ if (message.request.tool_name === 'StructuredOutput') {
185
+ const value = message.request.input;
186
+ const serialized = JSON.stringify(value);
187
+ if (responseSchema && value && typeof value === 'object' && !Array.isArray(value)
188
+ && Buffer.byteLength(serialized) <= 1024 * 1024 && validateStructuredOutput(value) === true) {
189
+ await assertModelRoutingText(serialized);
190
+ allowed = !expired();
191
+ }
192
+ } else allowed = await approve(message.request) === true;
193
+ } catch { allowed = false; }
194
+ permissions = false;
195
+ send({ type: 'control_response', response: { subtype: 'success', request_id: message.request_id,
196
+ response: allowed ? { behavior: 'allow', updatedInput: message.request.input } :
197
+ { behavior: 'deny', message: 'The host did not approve this tool request.' } } });
198
+ return;
199
+ }
200
+ if (message.session_id && message.session_id !== sessionId) return fail();
201
+ if (message.type === 'assistant' && !message.parent_tool_use_id) {
202
+ if (phase !== 'turn' || message.session_id !== sessionId || message.message?.model !== decision.model) return fail();
203
+ observed = true;
204
+ for (const block of message.message.content || []) if (block.type === 'text') assistantText.push(cleanText(block.text));
205
+ }
206
+ if (message.type === 'result') {
207
+ if (phase !== 'turn' || permissions || !observed || message.session_id !== sessionId || message.subtype !== 'success' ||
208
+ message.is_error !== false || message.permission_denials?.length || message.errors?.length) return fail();
209
+ if (responseSchema) {
210
+ try {
211
+ const value = message.structured_output;
212
+ if (!value || typeof value !== 'object' || Array.isArray(value) || validateStructuredOutput(value) !== true) return fail();
213
+ structuredAnswer = JSON.stringify(value);
214
+ if (Buffer.byteLength(structuredAnswer) > 1024 * 1024) return fail();
215
+ } catch { return fail(); }
216
+ } else if (typeof message.result !== 'string' || !message.result.trim()) return fail();
217
+ result = message; control('after');
218
+ }
219
+ };
220
+ child.stdout.on('data', chunk => {
221
+ if (done) return;
222
+ bytes += chunk.length; if (bytes > 16 * 1024 * 1024) return fail();
223
+ buffer += decoder.write(chunk);
224
+ let newline;
225
+ while ((newline = buffer.indexOf('\n')) !== -1 && !done) {
226
+ const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1);
227
+ try { void onMessage(JSON.parse(line)).catch(fail); } catch { fail(); }
228
+ }
229
+ });
230
+ child.stderr.on('data', () => {}); // Native diagnostics may contain credentials or prompt text.
231
+ child.stdin.on('error', fail); child.once('error', fail);
232
+ child.once('close', async code => {
233
+ if (done) return;
234
+ if (code !== 0 || phase !== 'exit' || !result || buffer.trim() || decoder.end()) return fail();
235
+ try {
236
+ await assertModelRoutingText(assistantText.join('\n'));
237
+ await assertModelRoutingText(responseSchema ? structuredAnswer : result.result);
238
+ if (done || expired()) return fail();
239
+ for (const text of assistantText) { if (expired()) return fail(); output(text); }
240
+ if (expired()) return fail();
241
+ receipt({ ts: new Date().toISOString(), harness: 'claude-code', status: 'completed', model: decision.model,
242
+ effort: decision.effort, taskClass: decision.taskClass, modelObserved: true, outputFormat: responseSchema ? 'json-schema' : 'text',
243
+ nativeSchemaRetries: responseSchema ? 'not-observed' : undefined,
244
+ evidence: 'assistant model observed; get_settings applied effort matched before and after turn; per-request effort not exposed' }, { env: clean });
245
+ done = true; clear(); resolve({ sessionId, decision, finalAnswer: responseSchema ? structuredAnswer : result.result, structuredOutput: Boolean(responseSchema),
246
+ nativeSchemaRetries: responseSchema ? 'not-observed' : undefined, modelObserved: true, effortSettingsObserved: true, perRequestEffortObserved: false });
247
+ } catch { fail(); }
248
+ });
249
+ control('initialize');
250
+ });
251
+ }
252
+
253
+ /** Native tool approvals are presented by this host; --print supplies no terminal dialogs. */
254
+ export async function launchControlledClaudeTerminal({ binary, args = [], input = process.stdin, output = process.stdout,
255
+ diagnostics = process.stderr, env = process.env, cwd = process.cwd(), signalSource = process, runTurn = runControlledClaudeTurn,
256
+ managedPrompt = runManagedPrompt } = {}) {
257
+ let sessionId, resume = false, initialPrompt, ownerBypass = false;
258
+ const remaining = [...args];
259
+ const invalid = () => new Error('Controlled Claude accepts only --resume <session UUID>, --permission-mode bypassPermissions, and a literal initial prompt.');
260
+ while (remaining[0]?.startsWith('-')) {
261
+ if (remaining[0] === '--resume' && !resume && uuid(remaining[1])) {
262
+ sessionId = remaining[1]; resume = true;
263
+ } else if (remaining[0] === '--permission-mode' && !ownerBypass && remaining[1] === 'bypassPermissions') {
264
+ ownerBypass = true;
265
+ } else throw invalid();
266
+ remaining.splice(0, 2);
267
+ }
268
+ if (remaining.some(arg => typeof arg !== 'string' || arg.startsWith('-'))) throw invalid();
269
+ if (remaining.length) initialPrompt = remaining.join(' ');
270
+ if (!input.isTTY || !output.isTTY) throw new Error('Controlled Claude requires a person at a terminal for prompts and approvals.');
271
+ const terminal = createManagedTerminal({ input, output });
272
+ const controller = new AbortController();
273
+ const cancel = () => controller.abort();
274
+ terminal.on('SIGINT', cancel);
275
+ for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.on(name, cancel);
276
+ diagnostics.write('Controlled Claude: one reviewed native allocation per prompt; tool approvals are answered here. Native Agent/Task workers are disabled; use the managed dispatcher for independent child work. /exit closes.\n');
277
+ if (ownerBypass) diagnostics.write('Owner permission bypass is active: native tool requests are approved by this host automatically. Agent/Task remain refused; routing and subscription guards remain active.\n');
278
+ try {
279
+ while (!controller.signal.aborted) {
280
+ const prompt = initialPrompt ?? await terminal.question('Claude> ', { signal: controller.signal }); initialPrompt = undefined;
281
+ if (prompt.trim() === '/exit') break;
282
+ if (!prompt.trim()) continue;
283
+ const deadline = Date.now() + 900000;
284
+ const turn = await managedPrompt({ binary, originalPrompt: prompt, harness: 'claude-code', projectRoot: cwd, deadline,
285
+ nativeContext: { sessionId, resume }, permissions: { apiBilling: false, write: ownerBypass },
286
+ primaryTurn: runTurn, cwd, env, signal: controller.signal,
287
+ output: text => output.write(text + '\n'), approve: async request => {
288
+ if (['Agent', 'Task'].includes(request.tool_name)) return false;
289
+ const details = cleanText(JSON.stringify({ tool: request.tool_name, input: request.input, reason: request.decision_reason }));
290
+ output.write(`Native permission request (untrusted tool text):\n${details}\n`);
291
+ if (ownerBypass) return true;
292
+ return (await terminal.question('Approve this tool request? Type yes: ', { signal: controller.signal, approval: true })).trim() === 'yes';
293
+ } });
294
+ sessionId = turn.sessionId; resume = true;
295
+ const actual = turn.modelObserved === true ? turn.decision : undefined;
296
+ const reviewer = turn.managedWorkflow?.executions?.find(item => item.workerId === 'independent-review');
297
+ if (actual?.model && actual?.effort) diagnostics.write(`Completed by ${cleanText(actual.model)} · applied ${cleanText(actual.effort)}${reviewer ? `; review ${cleanText(reviewer.observedModel)} · ${cleanText(reviewer.observedEffort)}` : ''}\n`);
298
+ diagnostics.write(`Native session: ${sessionId} (resume with --resume ${sessionId})\n`);
299
+ }
300
+ } finally { controller.abort(); terminal.close(); for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.removeListener(name, cancel); }
301
+ }
302
+
303
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
304
+ const args = process.argv.slice(2);
305
+ if (args[0] !== '--real-binary' || !args[1] || args[2] !== '--') {
306
+ process.stderr.write('Expected --real-binary /absolute/native/claude -- [--resume UUID]\n'); process.exitCode = 1;
307
+ } else launchControlledClaudeTerminal({ binary: args[1], args: args.slice(3) }).catch(() => {
308
+ process.stderr.write(REFUSED + '\n'); process.exitCode = 1;
309
+ });
310
+ }
@@ -0,0 +1,113 @@
1
+ // Controlled native exec/resume frontend. This is deliberately not the native Codex TUI.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import { performance } from 'node:perf_hooks';
6
+ import { createManagedTerminal } from './managed-terminal-input.mjs';
7
+ import { runManagedPrompt } from './model-managed-prompt.mjs';
8
+ import { decideNativeTurn, appendGatewayReceipt } from './model-routing-gateway.mjs';
9
+ import { validateDispatchDecision, subscriptionEnvironment } from './model-router-dispatch.mjs';
10
+ import { classifyTerminalArguments } from './model-terminal-gateway.mjs';
11
+ import { executeCodexWorkflowWorker } from './model-routing-execution-adapters.mjs';
12
+
13
+ const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
14
+ const BYPASS = '--dangerously-bypass-approvals-and-sandbox';
15
+ const invalid = () => new Error('Controlled Codex accepts a literal prompt, resume <UUID>, -C/--cd <directory>, and explicit owner permission bypass. Unsupported flags are refused.');
16
+
17
+ export function parseCodexManagedArguments(args = [], cwd = process.cwd()) {
18
+ if (!Array.isArray(args) || args.some(value => typeof value !== 'string')) throw invalid();
19
+ if (classifyTerminalArguments(args) === 'admin') return { administrative: true, args: [...args], cwd };
20
+ let sessionId, ownerBypass = false, literal = false;
21
+ const prompts = [];
22
+ for (let index = 0; index < args.length; index++) {
23
+ const arg = args[index];
24
+ if (literal) { prompts.push(arg); continue; }
25
+ if (arg === '--') { literal = true; continue; }
26
+ if (arg === BYPASS && !ownerBypass) { ownerBypass = true; continue; }
27
+ if (['-C', '--cd'].includes(arg)) {
28
+ const directory = args[++index]; if (!directory || directory.startsWith('-')) throw invalid();
29
+ cwd = fs.realpathSync(path.resolve(cwd, directory)); if (!fs.statSync(cwd).isDirectory()) throw invalid(); continue;
30
+ }
31
+ if (arg === 'resume' && !prompts.length && !sessionId) {
32
+ sessionId = args[++index]; if (!UUID.test(sessionId || '')) throw invalid(); continue;
33
+ }
34
+ if (arg.startsWith('-') || ['fork', 'agents'].includes(arg) && !prompts.length) throw invalid();
35
+ prompts.push(arg);
36
+ }
37
+ return { administrative: false, sessionId, ownerBypass, cwd: fs.realpathSync(cwd), initialPrompt: prompts.length ? prompts.join(' ') : undefined };
38
+ }
39
+
40
+ /** Allocation is selected for every ordinary parent turn; native rollout evidence is authoritative. */
41
+ export async function runCodexManagedPrimaryTurn({ binary, prompt, decisionPrompt = prompt, sessionId, threadId, cwd = process.cwd(),
42
+ env = process.env, readOnly = true, signal, timeoutMs = 900000, decide = decideNativeTurn,
43
+ verifyDecision = validateDispatchDecision, executeNative = executeCodexWorkflowWorker, receipt = appendGatewayReceipt,
44
+ output = () => {}, monotonic = () => performance.now() } = {}) {
45
+ if (!path.isAbsolute(binary || '') || typeof prompt !== 'string' || !prompt.trim() || prompt.length > 200000 ||
46
+ /^\s*\//.test(prompt) || !Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 900000 || signal?.aborted) throw invalid();
47
+ const id = sessionId ?? threadId;
48
+ if (id !== undefined && !UUID.test(id)) throw invalid();
49
+ const limit = monotonic() + timeoutMs;
50
+ const live = () => { if (signal?.aborted || monotonic() >= limit) throw new Error('Controlled Codex cancelled or absolute deadline exceeded'); };
51
+ const clean = subscriptionEnvironment(env);
52
+ const decision = await decide(decisionPrompt, 'codex', { env: clean });
53
+ live(); verifyDecision(decision); live();
54
+ const result = await executeNative({ binary, decision, prompt, cwd, readOnly, signal, timeoutMs: Math.max(1, limit - monotonic()),
55
+ env: { ...clean, RNB_TERMINAL_LAUNCH_ACTIVE: '1' }, sessionId: id });
56
+ live();
57
+ if (result?.completed !== true || result.modelObserved !== true || result.effortSettingsObserved !== true ||
58
+ result.model !== decision.model || result.effort !== decision.effort || !UUID.test(result.sessionId || '') ||
59
+ id !== undefined && result.sessionId !== id) throw new Error('Controlled Codex native model, effort or parent session unproven');
60
+ verifyDecision(decision); live();
61
+ receipt({ ts: new Date().toISOString(), harness: 'codex', status: 'completed', model: result.model, effort: result.effort,
62
+ taskClass: decision.taskClass, modelObserved: true, serviceMode: 'standard',
63
+ evidence: 'native turn_context model/effort and exact parent session observed; recursive native agents disabled' }, { env: clean });
64
+ if (result.answer) output(result.answer);
65
+ return { ...result, decision };
66
+ }
67
+
68
+ async function administrative(binary, args, env, cwd, signalSource, spawnNative) {
69
+ const child = spawnNative(binary, args, { cwd, env: { ...subscriptionEnvironment(env), RNB_TERMINAL_LAUNCH_ACTIVE: '1' }, shell: false, stdio: 'inherit' });
70
+ const handlers = new Map(['SIGINT', 'SIGTERM', 'SIGHUP'].map(signal => [signal, () => { try { child.kill(signal); } catch {} }]));
71
+ handlers.forEach((handler, signal) => signalSource.on(signal, handler));
72
+ try {
73
+ return await new Promise((resolve, reject) => { child.once('error', reject); child.once('exit', (code, signal) => resolve({ code, signal })); });
74
+ } finally { handlers.forEach((handler, signal) => signalSource.removeListener(signal, handler)); }
75
+ }
76
+
77
+ export async function launchCodexManagedTerminal({ binary, args = [], input = process.stdin, output = process.stdout,
78
+ diagnostics = process.stderr, cwd = process.cwd(), env = process.env, signalSource = process,
79
+ primaryTurn = runCodexManagedPrimaryTurn, managedPrompt = runManagedPrompt, spawnNative = spawn } = {}) {
80
+ if (env.RNB_TERMINAL_LAUNCH_ACTIVE) throw new Error('Controlled Codex recursive terminal invocation refused');
81
+ if (!path.isAbsolute(binary || '')) throw new Error('Absolute native Codex binary required');
82
+ const parsed = parseCodexManagedArguments(args, cwd);
83
+ if (parsed.administrative) return administrative(binary, args, env, parsed.cwd, signalSource, spawnNative);
84
+ if (!input.isTTY || !output.isTTY) throw new Error('Controlled Codex requires a person at a terminal');
85
+ let sessionId = parsed.sessionId, resume = Boolean(sessionId), initialPrompt = parsed.initialPrompt;
86
+ const terminal = createManagedTerminal({ input, output }), controller = new AbortController();
87
+ const cancel = () => controller.abort();
88
+ terminal.on('SIGINT', cancel);
89
+ for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.on(name, cancel);
90
+ diagnostics.write('Controlled Codex: native exec/resume with automatic managed workflows; native TUI and recursive agents unavailable. /exit closes.\n');
91
+ if (parsed.ownerBypass) diagnostics.write('Explicit owner bypass authorizes guarded workspace writes; unrestricted native sandbox bypass is not forwarded.\n');
92
+ try {
93
+ while (!controller.signal.aborted) {
94
+ const prompt = initialPrompt ?? await terminal.question('Codex> ', { signal: controller.signal }); initialPrompt = undefined;
95
+ if (prompt.trim() === '/exit') break;
96
+ if (!prompt.trim()) continue;
97
+ const turn = await managedPrompt({ binary, originalPrompt: prompt, harness: 'codex', projectRoot: parsed.cwd,
98
+ nativeContext: { sessionId, resume }, permissions: { apiBilling: false, write: parsed.ownerBypass },
99
+ primaryTurn, readOnly: !parsed.ownerBypass, cwd: parsed.cwd, env, signal: controller.signal,
100
+ output: text => output.write(String(text).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, '') + '\n') });
101
+ if (!UUID.test(turn?.sessionId || '')) throw new Error('Controlled Codex parent session unproven');
102
+ sessionId = turn.sessionId; resume = true;
103
+ const reviewer = turn.managedWorkflow?.executions?.find(item => item.workerId === 'independent-review');
104
+ const safe = value => String(value).replace(/[\x00-\x1f\x7f-\x9f]/g, '');
105
+ if (turn.modelObserved === true && turn.model && turn.effort) diagnostics.write(`Completed by ${safe(turn.model)} · ${safe(turn.effort)}${reviewer ? `; review ${safe(reviewer.observedModel)} · ${safe(reviewer.observedEffort)}` : ''}\n`);
106
+ diagnostics.write(`Native session: ${sessionId} (resume with resume ${sessionId})\n`);
107
+ }
108
+ return { code: 0, signal: null, sessionId };
109
+ } finally { controller.abort(); terminal.close(); for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.removeListener(name, cancel); }
110
+ }
111
+
112
+ // Canonical launcher API name; the controlled implementation is shared.
113
+ export const launchManagedCodexTerminal = launchCodexManagedTerminal;