mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-18 05:57:40 +02:00
fix(orchestration): integrate supported Codex worker invocation (#3434)
This commit is contained in:
@@ -48,20 +48,16 @@ fi
|
||||
|
||||
write_status "running" "- Task file: \`$task_file\`"
|
||||
|
||||
# SECURITY: never auto-approve agent tool execution. The worker prompt is built
|
||||
# from a task file that may contain LLM-generated or third-party content
|
||||
# (indirect prompt injection). `codex exec -p yolo` would execute
|
||||
# rm -rf / exfiltration commands without confirmation.
|
||||
# Default to the most restrictive approval mode; allow an explicit operator
|
||||
# override only via env (e.g. ECC_CODEX_APPROVAL_MODE=on-request for trusted runs).
|
||||
# Codex profiles (-p) and approval policies (--ask-for-approval) are
|
||||
# independent concepts. SECURITY: default to never approving untrusted
|
||||
# tool execution; operators can override via env.
|
||||
# Keep the configured sandbox; do not bypass approvals or sandboxing for task
|
||||
# files that may contain third-party content. Non-interactive runs default to
|
||||
# never prompting for approvals. This does not disable commands allowed by the
|
||||
# sandbox. Operators can request approvals with ECC_CODEX_APPROVAL_POLICY.
|
||||
# Approval policies (--ask-for-approval) are separate from Codex profiles (-p).
|
||||
APPROVAL_POLICY="${ECC_CODEX_APPROVAL_POLICY:-never}"
|
||||
case "$APPROVAL_POLICY" in
|
||||
never|on-request|on-failure) ;;
|
||||
never|on-request) ;;
|
||||
*)
|
||||
echo "[ECC worker] Refusing to run: unsupported ECC_CODEX_APPROVAL_POLICY='$APPROVAL_POLICY' (expected never|on-request|on-failure)" >&2
|
||||
echo "[ECC worker] Refusing to run: unsupported ECC_CODEX_APPROVAL_POLICY='$APPROVAL_POLICY' (expected never|on-request)" >&2
|
||||
write_status "failed" "- Error: unsupported approval policy"
|
||||
exit 1
|
||||
;;
|
||||
@@ -109,7 +105,9 @@ Task file: $task_file
|
||||
$(cat "$task_file")
|
||||
EOF
|
||||
|
||||
if codex exec --ask-for-approval "$APPROVAL_POLICY" -m gpt-5.4 --color never -C "$(pwd)" -o "$output_file" - < "$prompt_file"; then
|
||||
# Approval is a global option. Inherit the operator's configured model instead
|
||||
# of pinning a model that may be unavailable or retired.
|
||||
if codex --ask-for-approval "$APPROVAL_POLICY" exec --color never -C "$(pwd)" -o "$output_file" - < "$prompt_file"; then
|
||||
{
|
||||
echo "# Handoff"
|
||||
echo
|
||||
|
||||
@@ -24,6 +24,84 @@ function test(desc, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
function runWorker({ policy, exitCode = 0 } = {}) {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orch-worker-'));
|
||||
const binDir = path.join(tempRoot, 'bin');
|
||||
const captureFile = path.join(tempRoot, 'args.json');
|
||||
const taskFile = path.join(tempRoot, 'task.md');
|
||||
const handoffFile = path.join(tempRoot, 'handoff.md');
|
||||
const statusFile = path.join(tempRoot, 'status.md');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(binDir);
|
||||
fs.writeFileSync(taskFile, 'Make a focused change.');
|
||||
fs.writeFileSync(path.join(binDir, 'codex'), `#!${process.execPath}
|
||||
const fs = require('fs');
|
||||
const args = process.argv.slice(2);
|
||||
fs.writeFileSync(process.env.ECC_TEST_CAPTURE, JSON.stringify(args));
|
||||
// Approval is a global option, not an exec option in current Codex CLI.
|
||||
const execIndex = args.indexOf('exec');
|
||||
if (execIndex < 0 || args.indexOf('--ask-for-approval') > execIndex) {
|
||||
console.error("unexpected argument '--ask-for-approval' found");
|
||||
process.exit(2);
|
||||
}
|
||||
const outputIndex = args.indexOf('-o');
|
||||
fs.writeFileSync(args[outputIndex + 1], 'Completed the focused task.');
|
||||
process.exit(Number(process.env.ECC_TEST_EXIT));
|
||||
`, { mode: 0o755 });
|
||||
spawnSync('git', ['init'], { cwd: tempRoot, stdio: 'ignore' });
|
||||
const env = { ...process.env,
|
||||
PATH: `${binDir}${path.delimiter}${process.env.PATH}`,
|
||||
ECC_TEST_CAPTURE: captureFile,
|
||||
ECC_TEST_EXIT: String(exitCode)
|
||||
};
|
||||
if (policy === undefined) delete env.ECC_CODEX_APPROVAL_POLICY;
|
||||
else env.ECC_CODEX_APPROVAL_POLICY = policy;
|
||||
const result = spawnSync('bash', [SCRIPT, taskFile, handoffFile, statusFile], {
|
||||
cwd: tempRoot, env, encoding: 'utf8', timeout: 10000
|
||||
});
|
||||
return {
|
||||
...result,
|
||||
args: fs.existsSync(captureFile) ? JSON.parse(fs.readFileSync(captureFile, 'utf8')) : null,
|
||||
handoff: fs.existsSync(handoffFile) ? fs.readFileSync(handoffFile, 'utf8') : '',
|
||||
workerStatus: fs.readFileSync(statusFile, 'utf8')
|
||||
};
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test('starts a worker with the current CLI grammar and configured model', () => {
|
||||
const result = runWorker();
|
||||
assert.strictEqual(result.status, 0, result.stderr);
|
||||
assert.strictEqual(result.args[result.args.indexOf('--ask-for-approval') + 1], 'never');
|
||||
assert.ok(!result.args.includes('-m') && !result.args.includes('--model'),
|
||||
'Worker should inherit the configured model rather than pin a retired model');
|
||||
assert.ok(result.handoff.includes('Completed the focused task.'));
|
||||
assert.ok(result.workerStatus.includes('- State: completed'));
|
||||
});
|
||||
|
||||
test('forwards the explicit supported approval policy', () => {
|
||||
const result = runWorker({ policy: 'on-request' });
|
||||
assert.strictEqual(result.status, 0, result.stderr);
|
||||
assert.strictEqual(result.args[result.args.indexOf('--ask-for-approval') + 1], 'on-request');
|
||||
});
|
||||
|
||||
test('rejects the deprecated approval policy before invoking Codex', () => {
|
||||
const result = runWorker({ policy: 'on-failure' });
|
||||
assert.notStrictEqual(result.status, 0);
|
||||
assert.strictEqual(result.args, null);
|
||||
assert.ok(result.workerStatus.includes('unsupported approval policy'));
|
||||
});
|
||||
|
||||
test('records failure artifacts when a started worker exits nonzero', () => {
|
||||
const result = runWorker({ exitCode: 1 });
|
||||
assert.strictEqual(result.status, 1);
|
||||
assert.ok(result.args);
|
||||
assert.ok(result.workerStatus.includes('- State: failed'));
|
||||
assert.ok(result.handoff.includes('The Codex worker exited with a non-zero status.'));
|
||||
});
|
||||
|
||||
test('fails fast for an unreadable task file and records failure artifacts', () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-orch-worker-'));
|
||||
const handoffFile = path.join(tempRoot, '.orchestration', 'docs', 'handoff.md');
|
||||
|
||||
Reference in New Issue
Block a user