add gated codex goals support

This commit is contained in:
ejclaw
2026-05-01 23:21:24 +09:00
parent bb1998be29
commit fcd55a1c98
10 changed files with 333 additions and 17 deletions

View File

@@ -370,6 +370,79 @@ describe('prepareGroupEnvironment codex auth handling', () => {
});
});
describe('prepareGroupEnvironment Codex goals handling', () => {
let tempRoot: string;
let previousCwd: string;
let previousCodexGoals: string | undefined;
beforeEach(() => {
tempRoot = fs.mkdtempSync(path.join('/tmp', 'ejclaw-agent-env-goals-'));
previousCwd = process.cwd();
previousCodexGoals = process.env.CODEX_GOALS;
process.chdir(tempRoot);
process.env.EJ_TEST_ROOT = tempRoot;
process.env.EJ_TEST_HOME = path.join(tempRoot, 'home');
delete process.env.CODEX_GOALS;
fs.mkdirSync(path.join(process.env.EJ_TEST_HOME, '.codex'), {
recursive: true,
});
mockReadEnvFile.mockReset();
mockGetActiveCodexAuthPath.mockReset();
vi.mocked(config.isReviewService).mockReturnValue(false);
vi.mocked(serviceRouting.hasReviewerLease).mockReturnValue(false);
vi.mocked(serviceRouting.getEffectiveChannelLease).mockReturnValue({
chat_jid: 'dc:test',
owner_service_id: 'claude',
reviewer_service_id: 'codex-main',
arbiter_service_id: null,
owner_failover_active: false,
activated_at: null,
reason: null,
explicit: false,
});
});
afterEach(() => {
process.chdir(previousCwd);
delete process.env.EJ_TEST_ROOT;
delete process.env.EJ_TEST_HOME;
if (previousCodexGoals) process.env.CODEX_GOALS = previousCodexGoals;
else delete process.env.CODEX_GOALS;
fs.rmSync(tempRoot, { recursive: true, force: true });
});
it('keeps Codex goals disabled by default and enables them only via opt-in config', () => {
mockReadEnvFile.mockReturnValue({});
const defaultPrepared = prepareGroupEnvironment(group, false, 'dc:test');
expect(defaultPrepared.env.CODEX_GOALS).toBeUndefined();
const enabledPrepared = prepareGroupEnvironment(
{
...group,
agentConfig: {
codexGoals: true,
},
},
false,
'dc:test',
);
expect(enabledPrepared.env.CODEX_GOALS).toBe('true');
});
it('allows CODEX_GOALS env opt-in for Codex runner sessions', () => {
mockReadEnvFile.mockReturnValue({
CODEX_GOALS: 'true',
});
const prepared = prepareGroupEnvironment(group, false, 'dc:test');
expect(prepared.env.CODEX_GOALS).toBe('true');
});
});
describe('prepareReadonlySessionEnvironment codex compatibility', () => {
let tempRoot: string;
let previousCwd: string;

View File

@@ -319,6 +319,15 @@ function prepareCodexSessionEnvironment(args: {
process.env.CODEX_EFFORT;
if (codexEffort) args.env.CODEX_EFFORT = codexEffort;
const codexGoals =
args.group.agentConfig?.codexGoals ??
(args.envVars.CODEX_GOALS === 'true' || process.env.CODEX_GOALS === 'true');
if (codexGoals) {
args.env.CODEX_GOALS = 'true';
} else {
delete args.env.CODEX_GOALS;
}
const sessionCodexDir = path.join(args.sessionRootDir, '.codex');
syncHostCodexSessionFiles(sessionCodexDir);
@@ -527,6 +536,7 @@ export function prepareGroupEnvironment(
'CLAUDE_EFFORT',
'CODEX_MODEL',
'CODEX_EFFORT',
'CODEX_GOALS',
]);
const env = buildBaseRunnerEnv({

View File

@@ -680,3 +680,59 @@ OUROBOROS_LLM_BACKEND = "codex"
);
});
});
describe('agent-runner Codex goals', () => {
beforeEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
fakeProc = createFakeProcess();
vi.mocked(fs.existsSync).mockImplementation((p: fs.PathLike) => {
const str = String(p);
return (
str.includes('dist/index.js') ||
str.includes('dist/ipc-mcp-stdio.js') ||
str.endsWith('/.codex/config.toml')
);
});
});
afterEach(() => {
vi.useRealTimers();
});
it('passes Codex goal opt-in config through env and runner stdin payload', async () => {
let stdinPayload = '';
fakeProc.stdin.on('data', (chunk) => {
stdinPayload += chunk.toString();
});
const codexGroup: RegisteredGroup = {
...testGroup,
agentType: 'codex',
agentConfig: {
codexGoals: true,
},
};
const resultPromise = runAgentProcess(
codexGroup,
testInput,
() => {},
async () => {},
);
fakeProc.emit('close', 0);
const result = await resultPromise;
expect(result.status).toBe('success');
expect(JSON.parse(stdinPayload)).toMatchObject({
prompt: 'Hello',
codexGoals: true,
});
const spawnEnv = vi.mocked(spawn).mock.calls.at(-1)?.[2]?.env as
| Record<string, string>
| undefined;
expect(spawnEnv?.CODEX_GOALS).toBe('true');
});
});

View File

@@ -34,6 +34,7 @@ export interface AgentInput {
useTaskScopedSession?: boolean;
assistantName?: string;
agentType?: 'claude-code' | 'codex';
codexGoals?: boolean;
roomRoleContext?: RoomRoleContext;
}
@@ -150,13 +151,17 @@ export async function runAgentProcess(
onProcess(proc, processName, env.EJCLAW_IPC_DIR);
proc.stdin.write(JSON.stringify(input));
const runnerInput: AgentInput = {
...input,
...(group.agentConfig?.codexGoals === true ? { codexGoals: true } : {}),
};
proc.stdin.write(JSON.stringify(runnerInput));
proc.stdin.end();
runSpawnedAgentProcess({
proc,
group,
input,
input: runnerInput,
processName,
logsDir,
startTime,

View File

@@ -5,6 +5,7 @@ export interface AgentConfig {
// Per-group model/effort overrides (take precedence over global env vars)
codexModel?: string;
codexEffort?: string;
codexGoals?: boolean;
claudeModel?: string;
claudeEffort?: string;
claudeThinking?: 'adaptive' | 'enabled' | 'disabled';