mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-29 17:01:55 +00:00
TASK-pilot-executor-outcomes: require terminal executor evidence
This commit is contained in:
1 parent
a04d9c3625
commit
12e1e33cc8
7 files changed
+363
-14
No files matched your search
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
// Reduces a Codex protocol stream to the lifecycle facts TORCH may retain.
|
||||
// Raw turns, images, and stderr are drained but never copied into parent state.
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
const MAX_PROTOCOL_LINE_CHARS = 32 * 1024;
|
||||
const [command, ...args] = process.argv.slice(2);
|
||||
if (!command) process.exit(64);
|
||||
|
||||
const child = spawn(command, args, { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
let pending = '';
|
||||
let droppingOversizedLine = false;
|
||||
let threadStarted = null;
|
||||
let terminal = null;
|
||||
let childSpawnError = null;
|
||||
|
||||
function safeCode(value) {
|
||||
return typeof value === 'string' && /^[A-Z][A-Z0-9_.-]{0,79}$/.test(value) ? value : null;
|
||||
}
|
||||
|
||||
function emit(record) {
|
||||
process.stdout.write(`${JSON.stringify(record)}\n`);
|
||||
}
|
||||
|
||||
function inspectLine(line) {
|
||||
let record;
|
||||
try { record = JSON.parse(line); } catch { return; }
|
||||
if (!threadStarted && record?.type === 'thread.started'
|
||||
&& typeof (record.thread_id ?? record.threadId) === 'string'
|
||||
&& (record.thread_id ?? record.threadId).length <= 256) {
|
||||
threadStarted = record.thread_id ?? record.threadId;
|
||||
}
|
||||
if (record?.type === 'turn.completed') terminal = { type: 'turn.completed' };
|
||||
if (record?.type === 'turn.failed' || record?.type === 'turn.error' || record?.type === 'error') {
|
||||
const error = record.error && typeof record.error === 'object' ? record.error : record;
|
||||
const code = safeCode(error.code ?? record.code);
|
||||
terminal = { type: 'turn.failed', ...(code ? { code } : {}) };
|
||||
}
|
||||
}
|
||||
|
||||
function consume(chunk) {
|
||||
let cursor = 0;
|
||||
while (cursor < chunk.length) {
|
||||
const newline = chunk.indexOf('\n', cursor);
|
||||
const end = newline < 0 ? chunk.length : newline;
|
||||
const fragment = chunk.slice(cursor, end);
|
||||
if (!droppingOversizedLine) {
|
||||
if (pending.length + fragment.length > MAX_PROTOCOL_LINE_CHARS) {
|
||||
pending = '';
|
||||
droppingOversizedLine = true;
|
||||
} else pending += fragment;
|
||||
}
|
||||
if (newline < 0) return;
|
||||
if (!droppingOversizedLine) inspectLine(pending.trim());
|
||||
pending = '';
|
||||
droppingOversizedLine = false;
|
||||
cursor = newline + 1;
|
||||
}
|
||||
}
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout.on('data', consume);
|
||||
child.stderr.on('data', () => {});
|
||||
child.on('error', (error) => { childSpawnError = safeCode(error?.code) ?? 'EXECUTOR_SPAWN_ERROR'; });
|
||||
|
||||
const forwardedSignals = ['SIGTERM', 'SIGINT', 'SIGHUP'];
|
||||
for (const signal of forwardedSignals) {
|
||||
process.on(signal, () => { if (!child.killed) child.kill(signal); });
|
||||
}
|
||||
|
||||
function exitWithSignal(signal) {
|
||||
for (const name of forwardedSignals) process.removeAllListeners(name);
|
||||
process.kill(process.pid, signal);
|
||||
}
|
||||
|
||||
child.on('close', (code, signal) => {
|
||||
if (pending && !droppingOversizedLine) inspectLine(pending.trim());
|
||||
if (threadStarted) emit({ type: 'thread.started', thread_id: threadStarted });
|
||||
if (childSpawnError) emit({ type: 'error', code: childSpawnError });
|
||||
else if (terminal) emit(terminal);
|
||||
if (signal) exitWithSignal(signal);
|
||||
else process.exit(Number.isInteger(code) && code >= 0 ? code : 1);
|
||||
});
|
||||
@@ -281,6 +281,13 @@ export function planFleetUp({ repositoryRoot, controlPlane, adapter, adapters, f
|
||||
}
|
||||
validateRuntimeAdapter(runtimeAdapter);
|
||||
const identity = controlPlane.identity(area.id);
|
||||
if (identity.state === 'working') {
|
||||
blockers.push({
|
||||
areaId: area.id, code: 'RUNTIME_IDENTITY_ACTIVE', state: identity.state,
|
||||
runtimeSessionId: identity.runtimeSessionId ?? null,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const runtimeIntegration = runtimeAdapter.configure({
|
||||
repositoryRoot, areaId: area.id, mcpEntry: MCP_ENTRY,
|
||||
projectId: manifest.projectId, stateRoot,
|
||||
@@ -375,6 +382,15 @@ export function planAreaUp({ repositoryRoot, controlPlane, areaId, adapter, adap
|
||||
}
|
||||
validateRuntimeAdapter(runtimeAdapter);
|
||||
const identity = controlPlane.identity(area.id);
|
||||
if (identity.state === 'working') {
|
||||
return {
|
||||
action: 'area-up', projectId: manifest.projectId, areaId, fresh,
|
||||
actions: [], blockers: [{
|
||||
areaId, code: 'RUNTIME_IDENTITY_ACTIVE', state: identity.state,
|
||||
runtimeSessionId: identity.runtimeSessionId ?? null,
|
||||
}], canProceed: false, mutationPerformed: false,
|
||||
};
|
||||
}
|
||||
const runtimeIntegration = runtimeAdapter.configure({
|
||||
repositoryRoot, areaId: area.id, mcpEntry: MCP_ENTRY,
|
||||
projectId: manifest.projectId, stateRoot: localStateRoot(manifest),
|
||||
@@ -483,7 +499,20 @@ function safeExecutorCode(value) {
|
||||
return typeof value === 'string' && /^[A-Z][A-Z0-9_.-]{0,79}$/.test(value) ? value : null;
|
||||
}
|
||||
|
||||
export function classifyExecutorOutcome(result) {
|
||||
function codexTerminalState(result) {
|
||||
const output = boundedRuntimeCaptureOutput(result);
|
||||
let terminal = null;
|
||||
for (const line of output.split('\n')) {
|
||||
try {
|
||||
const type = JSON.parse(line)?.type;
|
||||
if (type === 'turn.completed') terminal = 'completed';
|
||||
else if (type === 'turn.failed' || type === 'turn.error' || type === 'error') terminal = 'failed';
|
||||
} catch { /* non-protocol output is not completion evidence */ }
|
||||
}
|
||||
return terminal;
|
||||
}
|
||||
|
||||
export function classifyExecutorOutcome(result, { runtime } = {}) {
|
||||
const status = Number.isInteger(result?.status) ? result.status : null;
|
||||
const signal = typeof result?.signal === 'string' && result.signal.trim() ? result.signal.trim() : null;
|
||||
const errorCode = safeExecutorCode(result?.error?.code);
|
||||
@@ -495,6 +524,12 @@ export function classifyExecutorOutcome(result) {
|
||||
};
|
||||
}
|
||||
if (signal) return { state: 'unknown', terminal: false, status, signal, reason: 'executor-signal' };
|
||||
if (status === 0 && runtime === 'codex' && codexTerminalState(result) !== 'completed') {
|
||||
return {
|
||||
state: 'unknown', terminal: false, status, signal: null,
|
||||
reason: 'codex-terminal-event-missing',
|
||||
};
|
||||
}
|
||||
if (status === 0) return { state: 'succeeded', terminal: true, status, signal: null };
|
||||
if (status === null) return { state: 'unknown', terminal: false, status: null, signal: null, reason: 'executor-status-missing' };
|
||||
return { state: 'failed', terminal: true, status, signal: null, reason: 'executor-nonzero-status' };
|
||||
@@ -536,8 +571,11 @@ export function startFleet({ plan, controlPlane, executor, adapters } = {}) {
|
||||
for (const action of plan.actions) {
|
||||
controlPlane.assertIdentity(action.areaId);
|
||||
writeCombinedPrompt(action);
|
||||
const result = executor({ ...action.launch, areaId: action.areaId, runtimeSessionId: action.runtimeSessionId });
|
||||
const outcome = classifyExecutorOutcome(result);
|
||||
const result = executor({
|
||||
...action.launch, areaId: action.areaId, runtime: action.runtime,
|
||||
runtimeSessionId: action.runtimeSessionId,
|
||||
});
|
||||
const outcome = classifyExecutorOutcome(result, { runtime: action.runtime });
|
||||
const runtimeAdapter = runtimes.get(action.runtime);
|
||||
if (outcome.state !== 'succeeded') {
|
||||
const runtimeSessionId = captureRuntimeId({ action, runtimeAdapter, result });
|
||||
@@ -577,7 +615,7 @@ export function startFleet({ plan, controlPlane, executor, adapters } = {}) {
|
||||
}
|
||||
try {
|
||||
runtimeSessionId = runtimeAdapter.captureRuntimeId({
|
||||
areaId: action.areaId, runtimeSessionId, stdout: result?.stdout, output: result?.output,
|
||||
areaId: action.areaId, runtimeSessionId, stdout: boundedRuntimeCaptureOutput(result),
|
||||
}).runtimeSessionId;
|
||||
} catch (error) {
|
||||
controlPlane.reportStatus({
|
||||
|
||||
Reference in new issue
Block a user