feat: add codex review failover and suppress output hardening

This commit is contained in:
Eyejoker
2026-03-28 05:40:38 +09:00
parent d1c693fb17
commit ba9f6871b6
49 changed files with 3884 additions and 1763 deletions

View File

@@ -9,8 +9,12 @@ const { mockReadEnvFile, mockGetActiveCodexAuthPath } = vi.hoisted(() => ({
}));
vi.mock('./config.js', () => ({
CODEX_REVIEW_SERVICE_ID: 'codex-review',
GROUPS_DIR: '/tmp/ejclaw-test-groups',
SERVICE_ID: 'codex-main',
SERVICE_SESSION_SCOPE: 'codex-main',
TIMEZONE: 'Asia/Seoul',
isReviewService: vi.fn(() => false),
}));
vi.mock('./db.js', () => ({
@@ -35,17 +39,31 @@ vi.mock('./platform-prompts.js', () => ({
readPairedRoomPrompt: vi.fn(() => 'paired room prompt'),
}));
vi.mock('./service-routing.js', () => ({
getEffectiveChannelLease: vi.fn(() => ({
chat_jid: 'dc:test',
owner_service_id: 'claude',
reviewer_service_id: 'codex-main',
activated_at: null,
reason: null,
explicit: false,
})),
}));
vi.mock('./group-folder.js', () => ({
resolveGroupFolderPath: (folder: string) =>
`${process.env.EJ_TEST_ROOT}/groups/${folder}`,
resolveGroupIpcPath: (folder: string) =>
`${process.env.EJ_TEST_ROOT}/ipc/${folder}`,
resolveGroupSessionsPath: (folder: string) =>
`${process.env.EJ_TEST_ROOT}/sessions/${folder}`,
resolveServiceGroupSessionsPath: (folder: string, serviceId: string) =>
`${process.env.EJ_TEST_ROOT}/sessions/${folder}/services/${serviceId}`,
resolveTaskRuntimeIpcPath: (folder: string, taskId: string) =>
`${process.env.EJ_TEST_ROOT}/task-ipc/${folder}/${taskId}`,
resolveTaskSessionsPath: (folder: string, taskId: string) =>
`${process.env.EJ_TEST_ROOT}/task-sessions/${folder}/${taskId}`,
resolveServiceTaskSessionsPath: (
folder: string,
serviceId: string,
taskId: string,
) => `${process.env.EJ_TEST_ROOT}/task-sessions/${folder}/${serviceId}/${taskId}`,
}));
vi.mock('os', async () => {
@@ -61,6 +79,9 @@ vi.mock('os', async () => {
});
import { prepareGroupEnvironment } from './agent-runner-environment.js';
import * as config from './config.js';
import * as db from './db.js';
import * as serviceRouting from './service-routing.js';
import type { RegisteredGroup } from './types.js';
const group: RegisteredGroup = {
@@ -131,6 +152,8 @@ describe('prepareGroupEnvironment codex auth handling', () => {
tempRoot,
'sessions',
group.folder,
'services',
'codex-main',
'.codex',
'auth.json',
);
@@ -165,6 +188,8 @@ describe('prepareGroupEnvironment codex auth handling', () => {
tempRoot,
'sessions',
group.folder,
'services',
'codex-main',
'.codex',
'auth.json',
);
@@ -172,4 +197,118 @@ describe('prepareGroupEnvironment codex auth handling', () => {
expect(auth).toEqual(rotatedAuth);
});
it('uses the failover prompt pack for codex-review when it owns an explicit failover lease', () => {
vi.mocked(config.isReviewService).mockReturnValue(true);
vi.mocked(db.isPairedRoomJid).mockReturnValue(true);
vi.mocked(serviceRouting.getEffectiveChannelLease).mockReturnValue({
chat_jid: 'dc:test',
owner_service_id: 'codex-review',
reviewer_service_id: 'codex-main',
activated_at: '2026-03-28T00:00:00.000Z',
reason: 'claude-429',
explicit: true,
});
mockReadEnvFile.mockReturnValue({});
const promptsDir = path.join(tempRoot, 'prompts');
fs.mkdirSync(promptsDir, { recursive: true });
fs.writeFileSync(
path.join(promptsDir, 'codex-review-platform.md'),
'review platform prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-paired-room.md'),
'review paired prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-failover-platform.md'),
'failover platform prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-failover-paired-room.md'),
'failover paired prompt\n',
);
prepareGroupEnvironment(
{ ...group, workDir: path.join(tempRoot, 'workdir') },
false,
'dc:test',
{
memoryBriefing: 'memory briefing',
},
);
const agentsPath = path.join(
tempRoot,
'sessions',
group.folder,
'services',
'codex-main',
'.codex',
'AGENTS.md',
);
const agents = fs.readFileSync(agentsPath, 'utf-8');
const segments = agents.trim().split('\n\n---\n\n');
expect(segments).toEqual([
'failover platform prompt',
'failover paired prompt',
'memory briefing',
]);
});
it('returns to the normal review prompt stack after failover is cleared', () => {
vi.mocked(config.isReviewService).mockReturnValue(true);
vi.mocked(db.isPairedRoomJid).mockReturnValue(true);
vi.mocked(serviceRouting.getEffectiveChannelLease).mockReturnValue({
chat_jid: 'dc:test',
owner_service_id: 'claude',
reviewer_service_id: 'codex-main',
activated_at: null,
reason: null,
explicit: false,
});
mockReadEnvFile.mockReturnValue({});
const promptsDir = path.join(tempRoot, 'prompts');
fs.mkdirSync(promptsDir, { recursive: true });
fs.writeFileSync(
path.join(promptsDir, 'codex-review-platform.md'),
'review platform prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-paired-room.md'),
'review paired prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-failover-platform.md'),
'failover platform prompt\n',
);
fs.writeFileSync(
path.join(promptsDir, 'codex-review-failover-paired-room.md'),
'failover paired prompt\n',
);
prepareGroupEnvironment(group, false, 'dc:test');
const agentsPath = path.join(
tempRoot,
'sessions',
group.folder,
'services',
'codex-main',
'.codex',
'AGENTS.md',
);
const agents = fs.readFileSync(agentsPath, 'utf-8');
const segments = agents.trim().split('\n\n---\n\n');
expect(segments).toEqual([
'platform prompt',
'review platform prompt',
'paired room prompt',
'review paired prompt',
]);
});
});