mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-11-03 03:11:57 +00:00
337 lines
13 KiB
JavaScript
337 lines
13 KiB
JavaScript
import { accessSync, constants, existsSync } from 'node:fs';
|
|
import { delimiter, join } from 'node:path';
|
|
import { TorchError } from '../kernel/errors.mjs';
|
|
import { profileCapabilityIssues, unsupportedCapability, validateRuntimeAdapter } from './runtime.mjs';
|
|
|
|
function findExecutable(name, env = process.env) {
|
|
for (const directory of (env.PATH ?? '').split(delimiter).filter(Boolean)) {
|
|
const candidate = join(directory, name);
|
|
try {
|
|
accessSync(candidate, constants.X_OK);
|
|
return candidate;
|
|
} catch { /* keep searching */ }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function text(value, name) {
|
|
if (typeof value !== 'string' || !value.trim()) {
|
|
throw new TorchError(`${name} must be a non-empty string`, {
|
|
code: 'INVALID_RUNTIME_INPUT', details: { field: name },
|
|
});
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
function optionalModelArgs(model) {
|
|
return typeof model === 'string' && model.trim() ? ['--model', model.trim()] : [];
|
|
}
|
|
|
|
function commandRecord(command, args, cwd) {
|
|
return { command, args, cwd, mutatesRuntime: true };
|
|
}
|
|
|
|
function mcpArgs(mcp) {
|
|
if (!mcp) return [];
|
|
if (typeof mcp.name !== 'string' || !/^[a-z0-9_-]+$/.test(mcp.name)
|
|
|| typeof mcp.command !== 'string' || !Array.isArray(mcp.args)) {
|
|
throw new TorchError('Codex MCP launch configuration is invalid', { code: 'INVALID_RUNTIME_INPUT' });
|
|
}
|
|
return [
|
|
'-c', `mcp_servers.${mcp.name}.command=${JSON.stringify(mcp.command)}`,
|
|
'-c', `mcp_servers.${mcp.name}.args=${JSON.stringify(mcp.args)}`,
|
|
];
|
|
}
|
|
|
|
function mcpName(areaId) {
|
|
return `torch-${areaId.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-|-$/g, '')}`;
|
|
}
|
|
|
|
function launchPolicyArgs(policy = {}) {
|
|
const issues = profileCapabilityIssues({ name: 'codex', capabilities: {
|
|
launchPolicy: {
|
|
fields: {
|
|
sandbox: ['read-only', 'workspace-write', 'danger-full-access'],
|
|
approval: ['approve-for-me', 'on-request', 'never'],
|
|
},
|
|
conflicts: [{ field: 'approval', value: 'approve-for-me', with: 'sandbox' }],
|
|
},
|
|
} }, { launchPolicy: policy });
|
|
if (issues.length) {
|
|
throw new TorchError('Codex launch policy is invalid or unsupported', {
|
|
code: 'RUNTIME_PROFILE_INVALID', details: { adapter: 'codex', issues },
|
|
});
|
|
}
|
|
const args = [];
|
|
if (policy.sandbox !== undefined) args.push('--sandbox', policy.sandbox);
|
|
if (policy.approval === 'approve-for-me') args.push('--approve-for-me');
|
|
else if (policy.approval !== undefined) args.push('--ask-for-approval', policy.approval);
|
|
return args;
|
|
}
|
|
|
|
const MAX_STARTUP_OUTPUT_BYTES = 16 * 1024;
|
|
const MAX_STARTUP_EVENT_COUNT = 64;
|
|
const MAX_DIAGNOSTIC_TEXT_LENGTH = 512;
|
|
|
|
function parseJsonLines(output) {
|
|
const records = [];
|
|
for (const line of String(output ?? '').split('\n').map((value) => value.trim()).filter(Boolean)) {
|
|
try { records.push(JSON.parse(line)); } catch { /* non-JSON diagnostics are not events */ }
|
|
}
|
|
return records;
|
|
}
|
|
|
|
function redactDiagnosticText(value, limit = MAX_DIAGNOSTIC_TEXT_LENGTH) {
|
|
if (typeof value !== 'string') return null;
|
|
const compact = value.replace(/\s+/g, ' ').trim();
|
|
if (!compact) return null;
|
|
return compact
|
|
.replace(/\bBearer\s+[-A-Za-z0-9._~+/]+=*/gi, 'Bearer [REDACTED]')
|
|
.replace(/((?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|password|secret|credential|authorization|cookie|prompt|private reasoning)\s*(?:=|:|\s)\s*)([^\s,;]+)/gi, '$1[REDACTED]')
|
|
.replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, '$1[REDACTED]@')
|
|
.slice(0, limit);
|
|
}
|
|
|
|
function boundedStartupOutput(output) {
|
|
const text = typeof output === 'string' ? output : String(output ?? '');
|
|
if (Buffer.byteLength(text, 'utf8') > MAX_STARTUP_OUTPUT_BYTES) {
|
|
return { records: null, reason: 'stdout-exceeds-bound' };
|
|
}
|
|
const lines = text.split('\n').map((value) => value.trim()).filter(Boolean);
|
|
if (lines.length > MAX_STARTUP_EVENT_COUNT) return { records: null, reason: 'stdout-too-many-events' };
|
|
const records = [];
|
|
for (const line of lines) {
|
|
try { records.push(JSON.parse(line)); } catch { return { records: null, reason: 'stdout-malformed-json' }; }
|
|
}
|
|
return { records, reason: null };
|
|
}
|
|
|
|
function startupFailureEvent(record) {
|
|
if (!record || typeof record !== 'object' || Array.isArray(record)) return null;
|
|
if (record.type === 'error' || record.type === 'turn.failed' || record.type === 'turn.error') {
|
|
const error = record.error && typeof record.error === 'object' && !Array.isArray(record.error)
|
|
? record.error : record;
|
|
return {
|
|
code: error.code ?? record.code,
|
|
category: error.category ?? record.category,
|
|
message: error.message ?? record.message,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function safeDiagnosticCode(value) {
|
|
return typeof value === 'string' && /^[A-Z][A-Z0-9_.-]{0,79}$/.test(value) ? value : null;
|
|
}
|
|
|
|
function safeDiagnosticCategory(value) {
|
|
return ['model-rejection', 'authentication', 'quota'].includes(value) ? value : null;
|
|
}
|
|
|
|
function categoryFromDiagnosticCode(code) {
|
|
if (!code) return null;
|
|
if (/(?:MODEL|UNSUPPORTED|UNAVAILABLE|NOT_FOUND)/.test(code)) return 'model-rejection';
|
|
if (/(?:AUTH|UNAUTHORIZED|FORBIDDEN|CREDENTIAL)/.test(code)) return 'authentication';
|
|
if (/(?:QUOTA|RATE_LIMIT|RATE-LIMIT|LIMIT_EXCEEDED)/.test(code)) return 'quota';
|
|
return null;
|
|
}
|
|
|
|
export function diagnoseCodexStartupFailure({ stdout, stderr } = {}) {
|
|
const parsed = boundedStartupOutput(stdout);
|
|
const safeStderr = redactDiagnosticText(stderr);
|
|
if (!parsed.records) {
|
|
return {
|
|
schema: 'torch.dev/runtime-startup-diagnostic/v1alpha1', outcome: 'unknown', reason: parsed.reason,
|
|
stderr: safeStderr,
|
|
};
|
|
}
|
|
const failure = parsed.records.map(startupFailureEvent).find(Boolean);
|
|
if (!failure) {
|
|
return {
|
|
schema: 'torch.dev/runtime-startup-diagnostic/v1alpha1', outcome: 'unknown', reason: 'stdout-no-failure-event',
|
|
stderr: safeStderr,
|
|
};
|
|
}
|
|
const message = redactDiagnosticText(failure.message);
|
|
const code = safeDiagnosticCode(failure.code);
|
|
const category = safeDiagnosticCategory(failure.category);
|
|
const modelRejection = /\b(model|unsupported|not supported|unavailable|not available|does not exist)\b/i.test(message ?? '')
|
|
|| /MODEL|UNSUPPORTED/i.test(code ?? '');
|
|
return {
|
|
schema: 'torch.dev/runtime-startup-diagnostic/v1alpha1', outcome: 'known',
|
|
category: category ?? categoryFromDiagnosticCode(code) ?? (modelRejection ? 'model-rejection' : 'runtime-error'),
|
|
...(code ? { code } : {}),
|
|
...(message ? { message } : {}),
|
|
stderr: safeStderr,
|
|
};
|
|
}
|
|
|
|
export class CodexRuntimeAdapter {
|
|
constructor({
|
|
env = process.env,
|
|
runner = null,
|
|
executable = 'codex',
|
|
nodeExecutable = process.execPath,
|
|
} = {}) {
|
|
this.name = 'codex';
|
|
this.env = env;
|
|
this.runner = runner;
|
|
this.executable = executable;
|
|
this.nodeExecutable = nodeExecutable;
|
|
this.configuration = Object.freeze({
|
|
model: 'gpt-6-luna', reasoning: 'high',
|
|
launchPolicy: Object.freeze({ approval: 'approve-for-me' }),
|
|
});
|
|
this.capabilities = Object.freeze({
|
|
detect: true,
|
|
configure: 'plan-only',
|
|
modelSelection: true,
|
|
reasoningSelection: true,
|
|
perInvocationCostCeiling: Object.freeze({ createSession: Object.freeze([]), resumeSession: Object.freeze([]) }),
|
|
launchPolicy: Object.freeze({
|
|
fields: Object.freeze({
|
|
sandbox: Object.freeze(['read-only', 'workspace-write', 'danger-full-access']),
|
|
approval: Object.freeze(['approve-for-me', 'on-request', 'never']),
|
|
}),
|
|
conflicts: Object.freeze([
|
|
Object.freeze({ field: 'approval', value: 'approve-for-me', with: 'sandbox' }),
|
|
]),
|
|
}),
|
|
createSession: true,
|
|
resumeSession: true,
|
|
sendOrSteer: 'native-with-durable-fallback',
|
|
getStatus: false,
|
|
listSessions: false,
|
|
stopSession: false,
|
|
captureRuntimeId: true,
|
|
installHooks: false,
|
|
removeHooks: false,
|
|
liveSteering: true,
|
|
durableMessaging: true,
|
|
processModel: 'resumable-turn',
|
|
});
|
|
validateRuntimeAdapter(this);
|
|
}
|
|
|
|
detect() {
|
|
const path = this.executable.includes('/') && existsSync(this.executable)
|
|
? this.executable : findExecutable(this.executable, this.env);
|
|
return { available: Boolean(path), executable: path, adapter: this.name };
|
|
}
|
|
|
|
configure({ repositoryRoot, areaId, mcpEntry } = {}) {
|
|
const root = text(repositoryRoot, 'repositoryRoot');
|
|
const id = text(areaId, 'areaId');
|
|
const entry = text(mcpEntry, 'mcpEntry');
|
|
return {
|
|
adapter: this.name,
|
|
mutationPerformed: false,
|
|
mcp: {
|
|
name: mcpName(id),
|
|
command: this.nodeExecutable,
|
|
args: [entry, '--root', root, '--area', id],
|
|
cwd: root,
|
|
},
|
|
note: 'Pass this identity-bound MCP server as an invocation-scoped Codex configuration.',
|
|
};
|
|
}
|
|
|
|
createSession({ areaId, worktree, promptFile, firstMessage, model, reasoning, launchPolicy, mcp } = {}) {
|
|
const id = text(areaId, 'areaId');
|
|
const cwd = text(worktree, 'worktree');
|
|
const args = [
|
|
'--cd', cwd, ...launchPolicyArgs(launchPolicy ?? this.configuration.launchPolicy),
|
|
...mcpArgs(mcp),
|
|
...((reasoning ?? this.configuration.reasoning) ? ['-c', `model_reasoning_effort=${JSON.stringify(text(reasoning ?? this.configuration.reasoning, 'reasoning'))}`] : []),
|
|
'exec', '--json',
|
|
...optionalModelArgs(model ?? this.configuration.model),
|
|
`${text(firstMessage, 'firstMessage')}\n\nRead and follow the TORCH domain prompt at ${text(promptFile, 'promptFile')}.`,
|
|
];
|
|
return {
|
|
adapter: this.name,
|
|
areaId: id,
|
|
runtimeSessionId: null,
|
|
requiresRuntimeIdCapture: true,
|
|
completionState: 'idle',
|
|
launch: commandRecord(this.executable, args, cwd),
|
|
};
|
|
}
|
|
|
|
resumeSession({ areaId, runtimeSessionId, worktree, message, instructionText, instructionDigest, model, reasoning, launchPolicy, mcp } = {}) {
|
|
const id = text(areaId, 'areaId');
|
|
const sessionId = text(runtimeSessionId, 'runtimeSessionId');
|
|
const cwd = text(worktree, 'worktree');
|
|
const currentInstructions = instructionText
|
|
? `\n\nAUTHORITATIVE CURRENT TORCH INSTRUCTIONS (sha256 ${text(instructionDigest, 'instructionDigest')}; replace stale briefing or conversation memory):\n${text(instructionText, 'instructionText')}`
|
|
: '';
|
|
const args = [
|
|
'--cd', cwd, ...launchPolicyArgs(launchPolicy ?? this.configuration.launchPolicy),
|
|
...mcpArgs(mcp),
|
|
...((reasoning ?? this.configuration.reasoning) ? ['-c', `model_reasoning_effort=${JSON.stringify(text(reasoning ?? this.configuration.reasoning, 'reasoning'))}`] : []),
|
|
'exec', 'resume', '--json',
|
|
...optionalModelArgs(model ?? this.configuration.model), sessionId, `${text(message, 'message')}${currentInstructions}`,
|
|
];
|
|
return {
|
|
adapter: this.name,
|
|
areaId: id,
|
|
runtimeSessionId: sessionId,
|
|
requiresRuntimeIdCapture: false,
|
|
completionState: 'idle',
|
|
launch: commandRecord(this.executable, args, cwd),
|
|
};
|
|
}
|
|
|
|
sendOrSteer({ controlPlane, sender = 'session-manager', recipient, body, references, runtimeSessionId } = {}) {
|
|
if (!controlPlane) {
|
|
throw new TorchError('Codex messaging requires the TORCH durable control plane', {
|
|
code: 'CONTROL_PLANE_REQUIRED',
|
|
});
|
|
}
|
|
const message = controlPlane.sendMessage({ sender, recipient, body, references });
|
|
if (this.runner && runtimeSessionId) {
|
|
try {
|
|
const result = this.runner(this.executable, [
|
|
'queue', '--thread', text(runtimeSessionId, 'runtimeSessionId'), '--message', text(body, 'body'),
|
|
]);
|
|
if (result?.status === undefined || result.status === 0) {
|
|
return { delivery: 'native+durable', liveSteeringAttempted: true, runtimeSessionId, message };
|
|
}
|
|
} catch { /* the durable message remains available for polling */ }
|
|
}
|
|
return { delivery: 'durable', liveSteeringAttempted: Boolean(this.runner && runtimeSessionId), message };
|
|
}
|
|
|
|
captureRuntimeId({ areaId, stdout, output, runtimeSessionId } = {}) {
|
|
if (runtimeSessionId) {
|
|
return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(runtimeSessionId, 'runtimeSessionId') };
|
|
}
|
|
const event = parseJsonLines(stdout ?? output).find((candidate) =>
|
|
candidate?.type === 'thread.started' && (candidate.thread_id || candidate.threadId));
|
|
const captured = event?.thread_id ?? event?.threadId;
|
|
if (!captured) {
|
|
throw new TorchError('Codex launch output did not contain a thread.started event', {
|
|
code: 'RUNTIME_ID_NOT_CAPTURED', details: { adapter: this.name },
|
|
});
|
|
}
|
|
return { areaId: text(areaId, 'areaId'), runtimeSessionId: text(captured, 'runtimeSessionId') };
|
|
}
|
|
|
|
diagnoseStartupFailure({ stdout, stderr } = {}) {
|
|
return diagnoseCodexStartupFailure({ stdout, stderr });
|
|
}
|
|
|
|
getStatus() { return unsupportedCapability(this.name, 'getStatus'); }
|
|
|
|
listSessions() { return unsupportedCapability(this.name, 'listSessions'); }
|
|
|
|
stopSession() { return unsupportedCapability(this.name, 'stopSession'); }
|
|
|
|
installHooks() { return unsupportedCapability(this.name, 'installHooks'); }
|
|
|
|
removeHooks() { return unsupportedCapability(this.name, 'removeHooks'); }
|
|
}
|
|
|
|
export function createCodexAdapter(options) {
|
|
return new CodexRuntimeAdapter(options);
|
|
}
|