ruvnet-brain 4.5.11 → 4.5.13
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.
- package/README.md +2 -2
- package/bin/install.mjs +15 -10
- package/config/model-router/policy.default.mjs +3 -3
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/codex-console-alias.mjs +88 -0
- package/plugin/scripts/session-start-budget.mjs +1 -1
- package/plugin/scripts/session-start-core.mjs +10 -2
- package/scripts/architecture-review-lock.mjs +44 -0
- package/scripts/candidate-ci-receipt.mjs +60 -0
- package/scripts/claude-controlled-terminal.mjs +68 -23
- package/scripts/codex-managed-terminal.mjs +113 -0
- package/scripts/grok-subscription-host.mjs +348 -0
- package/scripts/managed-terminal-input.mjs +152 -0
- package/scripts/model-managed-prompt.mjs +289 -0
- package/scripts/model-managed-workflow-service.mjs +327 -0
- package/scripts/model-routing-controller.mjs +368 -0
- package/scripts/model-routing-defence.mjs +44 -0
- package/scripts/model-routing-execution-adapters.mjs +318 -0
- package/scripts/model-routing-launchers.mjs +31 -0
- package/scripts/model-terminal-launchers.mjs +58 -14
- package/scripts/model-weekly-analyst.mjs +64 -28
- package/scripts/model-weekly-qualification.mjs +6 -1
- package/scripts/prepublication-evidence.mjs +7 -2
- package/scripts/qe/agentic-qe-4.3.mjs +12 -2
- package/scripts/release-qualification-contract.mjs +32 -3
- package/scripts/release-transaction-provider.mjs +10 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const assertModelRoutingText = async text => (await import('./model-routing-defence.mjs')).assertModelRoutingText(text);
|
|
2
3
|
// A controlled prompt boundary using native print/SDK controls, not the native terminal UI.
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import fs from 'node:fs';
|
|
5
6
|
import os from 'node:os';
|
|
6
7
|
import crypto from 'node:crypto';
|
|
7
8
|
import { spawn, execFileSync } from 'node:child_process';
|
|
8
|
-
import {
|
|
9
|
+
import { createManagedTerminal } from './managed-terminal-input.mjs';
|
|
9
10
|
import { pathToFileURL } from 'node:url';
|
|
10
11
|
import { StringDecoder } from 'node:string_decoder';
|
|
11
12
|
import { decideNativeTurn, appendGatewayReceipt } from './model-routing-gateway.mjs';
|
|
12
13
|
import { subscriptionEnvironment, assertSubscriptionAuth, validateDispatchDecision } from './model-router-dispatch.mjs';
|
|
13
14
|
import { validateClaudeTerminalSettings } from './model-terminal-launchers.mjs';
|
|
15
|
+
import { runManagedPrompt } from './model-managed-prompt.mjs';
|
|
14
16
|
|
|
15
17
|
const REFUSED = 'Controlled Claude turn refused; no fallback.';
|
|
16
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 || '');
|
|
@@ -91,11 +93,11 @@ export function retireControlledClaudeChild(child, { graceMs = 200, killMs = 200
|
|
|
91
93
|
});
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
export function controlledClaudeArguments(decision, sessionId, resume = false) {
|
|
96
|
+
export function controlledClaudeArguments(decision, sessionId, resume = false, responseSchema) {
|
|
95
97
|
if (decision?.harness !== 'claude-code' || decision.provider !== 'anthropic' || decision.subscriptionCovered !== true ||
|
|
96
98
|
!/^claude-[a-z0-9][a-z0-9.-]*$/.test(decision.model || '') ||
|
|
97
99
|
!['low', 'medium', 'high', 'xhigh', 'max'].includes(decision.effort) || !uuid(sessionId)) throw new Error(REFUSED);
|
|
98
|
-
return ['--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
|
|
100
|
+
return [...(responseSchema ? ['--json-schema', JSON.stringify(responseSchema)] : []), '--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
|
|
99
101
|
'--model', decision.model, '--effort', decision.effort, '--permission-mode', 'manual', '--permission-prompts', 'host',
|
|
100
102
|
'--permission-prompt-tool', 'stdio',
|
|
101
103
|
'--disallowedTools', 'Agent,Task',
|
|
@@ -103,14 +105,20 @@ export function controlledClaudeArguments(decision, sessionId, resume = false) {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/** One native process per routed turn. Permission requests require an explicit host answer. */
|
|
106
|
-
export async function runControlledClaudeTurn({ binary, prompt, sessionId = crypto.randomUUID(), resume = false,
|
|
108
|
+
export async function runControlledClaudeTurn({ binary, prompt, decisionPrompt = prompt, sessionId = crypto.randomUUID(), resume = false,
|
|
107
109
|
cwd = process.cwd(), env = process.env, decide = decideNativeTurn, verifyDecision = validateDispatchDecision,
|
|
108
110
|
checkSettings = validateClaudeTerminalSettings, checkAuth = assertSubscriptionAuth, spawnNative = spawn,
|
|
109
111
|
probe = execFileSync, approve = async () => false, output = () => {}, receipt = appendGatewayReceipt,
|
|
110
|
-
checkModules = assertClaudeModuleBoundary, signal, timeoutMs = 900000, handshakeMs = 10000 } = {}) {
|
|
112
|
+
checkModules = assertClaudeModuleBoundary, responseSchema, validateStructuredOutput, signal, timeoutMs = 900000, handshakeMs = 10000 } = {}) {
|
|
111
113
|
if (!path.isAbsolute(binary || '') || typeof prompt !== 'string' || !prompt.trim() || prompt.length > 200000 ||
|
|
112
114
|
/^\s*\//.test(prompt) || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 900000 ||
|
|
113
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);
|
|
114
122
|
// Extra body / effort env overrides can bypass the native CLI's requested allocation.
|
|
115
123
|
if (Object.keys(env).some(key => allocationEnv.test(key))) throw new Error(REFUSED);
|
|
116
124
|
const clean = subscriptionEnvironment(env);
|
|
@@ -118,17 +126,20 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
118
126
|
if (fs.existsSync(managed)) throw new Error('Managed Claude settings require separate module-boundary qualification.');
|
|
119
127
|
checkSettings({ env: clean, cwd, home: env.HOME || os.homedir() });
|
|
120
128
|
checkAuth('claude-code', { env: clean, probe: (_name, args, options) => probe(binary, args, options) });
|
|
121
|
-
const decision = await decide(
|
|
129
|
+
const decision = await decide(decisionPrompt, 'claude-code', { env: clean });
|
|
122
130
|
verifyDecision(decision);
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
if (expired()) throw new Error(REFUSED);
|
|
132
|
+
const args = controlledClaudeArguments(decision, sessionId, resume, responseSchema);
|
|
133
|
+
if (expired()) throw new Error(REFUSED);
|
|
125
134
|
const child = spawnNative(binary, args, { cwd, env: clean, shell: false, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
126
135
|
return new Promise((resolve, reject) => {
|
|
127
136
|
let done = false, phase = 'initialize', buffer = '', bytes = 0, result, observed = false, permissions = false;
|
|
137
|
+
const assistantText = [];
|
|
138
|
+
let structuredAnswer;
|
|
128
139
|
const decoder = new StringDecoder('utf8');
|
|
129
140
|
const ids = { initialize: crypto.randomUUID(), before: crypto.randomUUID(), after: crypto.randomUUID() };
|
|
130
141
|
let handshake;
|
|
131
|
-
const timer = setTimeout(() => fail(),
|
|
142
|
+
const timer = setTimeout(() => fail(), Math.max(1, limit - performance.now()));
|
|
132
143
|
const abort = () => fail();
|
|
133
144
|
signal?.addEventListener('abort', abort, { once: true });
|
|
134
145
|
const clear = () => { clearTimeout(timer); clearTimeout(handshake); signal?.removeEventListener('abort', abort); };
|
|
@@ -137,7 +148,7 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
137
148
|
done = true; clear();
|
|
138
149
|
void retireControlledClaudeChild(child).then(() => reject(new Error(REFUSED)));
|
|
139
150
|
};
|
|
140
|
-
const send = message => { if (
|
|
151
|
+
const send = message => { if (done) return; if (expired()) return fail(); child.stdin.write(JSON.stringify(message) + '\n'); };
|
|
141
152
|
const control = which => {
|
|
142
153
|
phase = which; clearTimeout(handshake); handshake = setTimeout(fail, handshakeMs);
|
|
143
154
|
send({ type: 'control_request', request_id: ids[which], request: { subtype: which === 'initialize' ? 'initialize' : 'get_settings' } });
|
|
@@ -146,6 +157,7 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
146
157
|
Array.isArray(value.sources) && !value.sources.some(source => source.source === 'policySettings' && Object.keys(source.settings || {}).length);
|
|
147
158
|
const onMessage = async message => {
|
|
148
159
|
if (done) return;
|
|
160
|
+
if (expired()) return fail();
|
|
149
161
|
if (message.type === 'control_response') {
|
|
150
162
|
const response = message.response;
|
|
151
163
|
if (response?.request_id !== ids[phase] || response.subtype !== 'success') return fail();
|
|
@@ -168,7 +180,17 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
168
180
|
if (['Agent', 'Task'].includes(message.request.tool_name)) return fail();
|
|
169
181
|
permissions = true;
|
|
170
182
|
let allowed = false;
|
|
171
|
-
try {
|
|
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; }
|
|
172
194
|
permissions = false;
|
|
173
195
|
send({ type: 'control_response', response: { subtype: 'success', request_id: message.request_id,
|
|
174
196
|
response: allowed ? { behavior: 'allow', updatedInput: message.request.input } :
|
|
@@ -177,13 +199,21 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
177
199
|
}
|
|
178
200
|
if (message.session_id && message.session_id !== sessionId) return fail();
|
|
179
201
|
if (message.type === 'assistant' && !message.parent_tool_use_id) {
|
|
180
|
-
if (phase !== 'turn' || message.message?.model !== decision.model) return fail();
|
|
202
|
+
if (phase !== 'turn' || message.session_id !== sessionId || message.message?.model !== decision.model) return fail();
|
|
181
203
|
observed = true;
|
|
182
|
-
for (const block of message.message.content || []) if (block.type === 'text')
|
|
204
|
+
for (const block of message.message.content || []) if (block.type === 'text') assistantText.push(cleanText(block.text));
|
|
183
205
|
}
|
|
184
206
|
if (message.type === 'result') {
|
|
185
207
|
if (phase !== 'turn' || permissions || !observed || message.session_id !== sessionId || message.subtype !== 'success' ||
|
|
186
|
-
message.is_error || message.permission_denials?.length || message.errors?.length) return fail();
|
|
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();
|
|
187
217
|
result = message; control('after');
|
|
188
218
|
}
|
|
189
219
|
};
|
|
@@ -199,14 +229,21 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
199
229
|
});
|
|
200
230
|
child.stderr.on('data', () => {}); // Native diagnostics may contain credentials or prompt text.
|
|
201
231
|
child.stdin.on('error', fail); child.once('error', fail);
|
|
202
|
-
child.once('close', code => {
|
|
232
|
+
child.once('close', async code => {
|
|
203
233
|
if (done) return;
|
|
204
234
|
if (code !== 0 || phase !== 'exit' || !result || buffer.trim() || decoder.end()) return fail();
|
|
205
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();
|
|
206
241
|
receipt({ ts: new Date().toISOString(), harness: 'claude-code', status: 'completed', model: decision.model,
|
|
207
|
-
effort: decision.effort, taskClass: decision.taskClass, modelObserved: true,
|
|
242
|
+
effort: decision.effort, taskClass: decision.taskClass, modelObserved: true, outputFormat: responseSchema ? 'json-schema' : 'text',
|
|
243
|
+
nativeSchemaRetries: responseSchema ? 'not-observed' : undefined,
|
|
208
244
|
evidence: 'assistant model observed; get_settings applied effort matched before and after turn; per-request effort not exposed' }, { env: clean });
|
|
209
|
-
done = true; clear(); resolve({ sessionId, decision,
|
|
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 });
|
|
210
247
|
} catch { fail(); }
|
|
211
248
|
});
|
|
212
249
|
control('initialize');
|
|
@@ -215,7 +252,8 @@ export async function runControlledClaudeTurn({ binary, prompt, sessionId = cryp
|
|
|
215
252
|
|
|
216
253
|
/** Native tool approvals are presented by this host; --print supplies no terminal dialogs. */
|
|
217
254
|
export async function launchControlledClaudeTerminal({ binary, args = [], input = process.stdin, output = process.stdout,
|
|
218
|
-
diagnostics = process.stderr, env = process.env, cwd = process.cwd(),
|
|
255
|
+
diagnostics = process.stderr, env = process.env, cwd = process.cwd(), signalSource = process, runTurn = runControlledClaudeTurn,
|
|
256
|
+
managedPrompt = runManagedPrompt } = {}) {
|
|
219
257
|
let sessionId, resume = false, initialPrompt, ownerBypass = false;
|
|
220
258
|
const remaining = [...args];
|
|
221
259
|
const invalid = () => new Error('Controlled Claude accepts only --resume <session UUID>, --permission-mode bypassPermissions, and a literal initial prompt.');
|
|
@@ -230,29 +268,36 @@ export async function launchControlledClaudeTerminal({ binary, args = [], input
|
|
|
230
268
|
if (remaining.some(arg => typeof arg !== 'string' || arg.startsWith('-'))) throw invalid();
|
|
231
269
|
if (remaining.length) initialPrompt = remaining.join(' ');
|
|
232
270
|
if (!input.isTTY || !output.isTTY) throw new Error('Controlled Claude requires a person at a terminal for prompts and approvals.');
|
|
233
|
-
const terminal =
|
|
271
|
+
const terminal = createManagedTerminal({ input, output });
|
|
234
272
|
const controller = new AbortController();
|
|
235
273
|
const cancel = () => controller.abort();
|
|
236
274
|
terminal.on('SIGINT', cancel);
|
|
275
|
+
for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.on(name, cancel);
|
|
237
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');
|
|
238
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');
|
|
239
278
|
try {
|
|
240
279
|
while (!controller.signal.aborted) {
|
|
241
|
-
const prompt = initialPrompt ?? await terminal.question('Claude> '); initialPrompt = undefined;
|
|
280
|
+
const prompt = initialPrompt ?? await terminal.question('Claude> ', { signal: controller.signal }); initialPrompt = undefined;
|
|
242
281
|
if (prompt.trim() === '/exit') break;
|
|
243
282
|
if (!prompt.trim()) continue;
|
|
244
|
-
const
|
|
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,
|
|
245
287
|
output: text => output.write(text + '\n'), approve: async request => {
|
|
246
288
|
if (['Agent', 'Task'].includes(request.tool_name)) return false;
|
|
247
289
|
const details = cleanText(JSON.stringify({ tool: request.tool_name, input: request.input, reason: request.decision_reason }));
|
|
248
290
|
output.write(`Native permission request (untrusted tool text):\n${details}\n`);
|
|
249
291
|
if (ownerBypass) return true;
|
|
250
|
-
return (await terminal.question('Approve this tool request? Type yes: ')).trim() === 'yes';
|
|
292
|
+
return (await terminal.question('Approve this tool request? Type yes: ', { signal: controller.signal, approval: true })).trim() === 'yes';
|
|
251
293
|
} });
|
|
252
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`);
|
|
253
298
|
diagnostics.write(`Native session: ${sessionId} (resume with --resume ${sessionId})\n`);
|
|
254
299
|
}
|
|
255
|
-
} finally { terminal.close(); }
|
|
300
|
+
} finally { controller.abort(); terminal.close(); for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) signalSource.removeListener(name, cancel); }
|
|
256
301
|
}
|
|
257
302
|
|
|
258
303
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
@@ -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;
|