[codex] Remove Codex SDK canary runtime (#220)
* Revert "fix: drain sdk follow-up turns (#219)" This reverts commit807828e0ce. * Revert "feat: feature-flag Codex SDK runner (#218)" This reverts commit879d16235f.
This commit is contained in:
@@ -852,53 +852,4 @@ describe('agent-runner Codex goals', () => {
|
||||
| undefined;
|
||||
expect(spawnEnv?.CODEX_GOALS).toBe('true');
|
||||
});
|
||||
|
||||
it('marks SDK codex processes so queued IPC follow-ups drain after the active run', async () => {
|
||||
const previousCodexGoals = process.env.CODEX_GOALS;
|
||||
delete process.env.CODEX_GOALS;
|
||||
const onProcess = vi.fn();
|
||||
const codexGroup: RegisteredGroup = {
|
||||
...testGroup,
|
||||
agentType: 'codex',
|
||||
};
|
||||
|
||||
try {
|
||||
const resultPromise = runAgentProcess(
|
||||
codexGroup,
|
||||
{
|
||||
...testInput,
|
||||
roomRoleContext: {
|
||||
serviceId: 'codex-main',
|
||||
role: 'owner',
|
||||
ownerServiceId: 'codex-main',
|
||||
reviewerServiceId: 'claude',
|
||||
failoverOwner: false,
|
||||
},
|
||||
},
|
||||
onProcess,
|
||||
async () => {},
|
||||
{
|
||||
CODEX_RUNTIME: 'sdk',
|
||||
CODEX_RUNTIME_SDK_ROLES: 'owner',
|
||||
},
|
||||
);
|
||||
|
||||
fakeProc.emit('close', 0);
|
||||
const result = await resultPromise;
|
||||
expect(result.status).toBe('success');
|
||||
|
||||
expect(onProcess).toHaveBeenCalledWith(
|
||||
fakeProc,
|
||||
expect.any(String),
|
||||
expect.any(String),
|
||||
{ drainFollowUpsAfterRun: true },
|
||||
);
|
||||
} finally {
|
||||
if (previousCodexGoals === undefined) {
|
||||
delete process.env.CODEX_GOALS;
|
||||
} else {
|
||||
process.env.CODEX_GOALS = previousCodexGoals;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,30 +61,6 @@ export interface AgentOutput {
|
||||
};
|
||||
}
|
||||
|
||||
interface ActiveProcessOptions {
|
||||
drainFollowUpsAfterRun?: boolean;
|
||||
}
|
||||
|
||||
function isCodexSdkRuntimeMode(
|
||||
env: Record<string, string>,
|
||||
input: AgentInput,
|
||||
): boolean {
|
||||
const runtime = env.CODEX_RUNTIME?.trim().toLowerCase();
|
||||
if (runtime !== 'sdk') return false;
|
||||
if (input.prompt.trim() === '/compact') return false;
|
||||
if (input.codexGoals === true || env.CODEX_GOALS === 'true') return false;
|
||||
|
||||
const rawRoles = env.CODEX_RUNTIME_SDK_ROLES?.trim();
|
||||
if (!rawRoles) return true;
|
||||
const role = input.roomRoleContext?.role?.trim().toLowerCase();
|
||||
if (!role) return false;
|
||||
return rawRoles
|
||||
.split(',')
|
||||
.map((item) => item.trim().toLowerCase())
|
||||
.filter(Boolean)
|
||||
.includes(role);
|
||||
}
|
||||
|
||||
function readRoomSkillOverridesForRunner(
|
||||
chatJid: string,
|
||||
): StoredRoomSkillOverride[] {
|
||||
@@ -150,7 +126,6 @@ export async function runAgentProcess(
|
||||
proc: ChildProcess,
|
||||
processName: string,
|
||||
runtimeIpcDir: string,
|
||||
options?: ActiveProcessOptions,
|
||||
) => void,
|
||||
onOutput?: (output: AgentOutput) => Promise<void>,
|
||||
envOverrides?: Record<string, string>,
|
||||
@@ -211,11 +186,6 @@ export async function runAgentProcess(
|
||||
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
const processSuffix = input.runId || `${Date.now()}`;
|
||||
const processName = `ejclaw-${safeName}-${processSuffix}`;
|
||||
const activeProcessOptions: ActiveProcessOptions = {
|
||||
drainFollowUpsAfterRun:
|
||||
(group.agentType || 'claude-code') === 'codex' &&
|
||||
isCodexSdkRuntimeMode(env, input),
|
||||
};
|
||||
const finalizeCodexAuthSessionOnce = createCodexAuthSessionFinalizer(
|
||||
() => codexSessionAuth,
|
||||
);
|
||||
@@ -258,7 +228,7 @@ export async function runAgentProcess(
|
||||
env,
|
||||
});
|
||||
|
||||
onProcess(proc, processName, env[EJCLAW_ENV.ipcDir], activeProcessOptions);
|
||||
onProcess(proc, processName, env[EJCLAW_ENV.ipcDir]);
|
||||
|
||||
const runnerInput: AgentInput = {
|
||||
...input,
|
||||
|
||||
@@ -37,7 +37,6 @@ export interface GroupState {
|
||||
pendingTasks: QueuedTask[];
|
||||
process: ChildProcess | null;
|
||||
processName: string | null;
|
||||
drainFollowUpsAfterRun: boolean;
|
||||
ipcDir: string | null;
|
||||
retryCount: number;
|
||||
retryTimer: ReturnType<typeof setTimeout> | null;
|
||||
@@ -61,7 +60,6 @@ export function createGroupState(): GroupState {
|
||||
pendingTasks: [],
|
||||
process: null,
|
||||
processName: null,
|
||||
drainFollowUpsAfterRun: false,
|
||||
ipcDir: null,
|
||||
retryCount: 0,
|
||||
retryTimer: null,
|
||||
@@ -147,7 +145,6 @@ export function resetRunState(state: GroupState, groupJid: string): void {
|
||||
state.startedAt = null;
|
||||
state.process = null;
|
||||
state.processName = null;
|
||||
state.drainFollowUpsAfterRun = false;
|
||||
state.ipcDir = null;
|
||||
state.directTerminalDeliveries.clear();
|
||||
transitionRunPhase(state, groupJid, 'idle');
|
||||
@@ -163,8 +160,7 @@ export function assertRunPhaseInvariants(
|
||||
state.currentRunId != null ||
|
||||
state.runningTaskId != null ||
|
||||
state.process != null ||
|
||||
state.processName != null ||
|
||||
state.drainFollowUpsAfterRun
|
||||
state.processName != null
|
||||
) {
|
||||
logger.error(
|
||||
{
|
||||
@@ -174,7 +170,6 @@ export function assertRunPhaseInvariants(
|
||||
runningTaskId: state.runningTaskId,
|
||||
hasProcess: state.process != null,
|
||||
processName: state.processName,
|
||||
drainFollowUpsAfterRun: state.drainFollowUpsAfterRun,
|
||||
},
|
||||
'Invariant violation: idle phase has stale run/task ID or process',
|
||||
);
|
||||
|
||||
@@ -40,6 +40,8 @@ describe('GroupQueue', () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
// --- Single group at a time ---
|
||||
|
||||
it('only runs one agent per group at a time', async () => {
|
||||
let concurrentCount = 0;
|
||||
let maxConcurrent = 0;
|
||||
@@ -88,7 +90,6 @@ describe('GroupQueue', () => {
|
||||
|
||||
releaseRun(true);
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
expect(processMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not pipe follow-up messages after stdin close was requested', async () => {
|
||||
@@ -790,46 +791,3 @@ describe('GroupQueue close reason tracking', () => {
|
||||
expect(queue.getCloseReasonForRun('group1@g.us', activeRunId)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GroupQueue SDK follow-up draining', () => {
|
||||
let queue: GroupQueue;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.clearAllMocks();
|
||||
queue = new GroupQueue();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('drains follow-up messages after active processes that cannot consume queued IPC', async () => {
|
||||
const ipcDir = '/tmp/ejclaw-test-data/ipc/group-folder';
|
||||
let releaseRun!: (value: boolean) => void;
|
||||
let activeRun = true;
|
||||
const blocker = new Promise<boolean>((resolve) => {
|
||||
releaseRun = resolve;
|
||||
});
|
||||
|
||||
const processMessages = vi.fn(async () => {
|
||||
if (!activeRun) return true;
|
||||
activeRun = false;
|
||||
return await blocker;
|
||||
});
|
||||
|
||||
queue.setProcessMessagesFn(processMessages);
|
||||
queue.enqueueMessageCheck('group1@g.us', ipcDir);
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
|
||||
queue.registerProcess('group1@g.us', {} as any, 'sdk-agent', ipcDir, {
|
||||
drainFollowUpsAfterRun: true,
|
||||
});
|
||||
expect(queue.sendMessage('group1@g.us', '후속 메시지')).toBe(true);
|
||||
|
||||
releaseRun(true);
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
|
||||
expect(processMessages).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,6 +65,7 @@ export class GroupQueue {
|
||||
this.processMessagesFn = fn;
|
||||
}
|
||||
|
||||
/** Limit concurrency after restart to avoid API rate-limit storms. */
|
||||
enterRecoveryMode(): void {
|
||||
this.recoveryMode = true;
|
||||
logger.info(
|
||||
@@ -100,6 +101,7 @@ export class GroupQueue {
|
||||
|
||||
const state = this.getGroup(groupJid);
|
||||
|
||||
// Pre-set IPC dir so sendMessage can pipe follow-ups while agent process starts
|
||||
if (ipcDir && !state.ipcDir) {
|
||||
state.ipcDir = ipcDir;
|
||||
}
|
||||
@@ -155,6 +157,7 @@ export class GroupQueue {
|
||||
|
||||
const state = this.getGroup(groupJid);
|
||||
|
||||
// Prevent double-queuing: check both pending and currently-running task
|
||||
if (state.runningTaskId === taskId) {
|
||||
logger.debug({ groupJid, taskId }, 'Task already running, skipping');
|
||||
return;
|
||||
@@ -193,6 +196,7 @@ export class GroupQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run immediately
|
||||
this.runTask(groupJid, { id: taskId, groupJid, fn }).catch((err) =>
|
||||
logger.error({ groupJid, taskId, err }, 'Unhandled error in runTask'),
|
||||
);
|
||||
@@ -203,12 +207,10 @@ export class GroupQueue {
|
||||
proc: ChildProcess,
|
||||
processName: string,
|
||||
ipcDir?: string,
|
||||
options?: { drainFollowUpsAfterRun?: boolean },
|
||||
): void {
|
||||
const state = this.getGroup(groupJid);
|
||||
state.process = proc;
|
||||
state.processName = processName;
|
||||
state.drainFollowUpsAfterRun = options?.drainFollowUpsAfterRun === true;
|
||||
if (ipcDir) state.ipcDir = ipcDir;
|
||||
logger.info(
|
||||
{
|
||||
@@ -239,7 +241,6 @@ export class GroupQueue {
|
||||
|
||||
try {
|
||||
const filename = queueFollowUpMessage(state.ipcDir, text);
|
||||
state.pendingMessages ||= state.drainFollowUpsAfterRun;
|
||||
logger.info(
|
||||
{
|
||||
groupJid,
|
||||
@@ -247,7 +248,6 @@ export class GroupQueue {
|
||||
ipcDir: state.ipcDir,
|
||||
textLength: text.length,
|
||||
filename,
|
||||
pendingMessages: state.pendingMessages,
|
||||
},
|
||||
'Queued follow-up message for active agent',
|
||||
);
|
||||
|
||||
@@ -100,7 +100,6 @@ interface RunMessageAgentAttemptArgs {
|
||||
: never,
|
||||
processName: string,
|
||||
ipcDir?: string,
|
||||
options?: { drainFollowUpsAfterRun?: boolean },
|
||||
) => void;
|
||||
onOutput?: (output: AgentOutput) => Promise<void>;
|
||||
pairedExecutionLifecycle: {
|
||||
|
||||
@@ -274,8 +274,8 @@ export async function runAgentForGroup(
|
||||
deps.persistSession(sessionFolder, sessionId);
|
||||
currentSessionId = sessionId;
|
||||
},
|
||||
registerProcess: (proc, processName, ipcDir, options) =>
|
||||
deps.queue.registerProcess(chatJid, proc, processName, ipcDir, options),
|
||||
registerProcess: (proc, processName, ipcDir) =>
|
||||
deps.queue.registerProcess(chatJid, proc, processName, ipcDir),
|
||||
onOutput,
|
||||
pairedExecutionLifecycle,
|
||||
log,
|
||||
|
||||
Reference in New Issue
Block a user