fix: release Codex lease after final output
This commit is contained in:
@@ -22,6 +22,11 @@ interface RunSpawnedAgentProcessArgs {
|
|||||||
logsDir: string;
|
logsDir: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
onOutput?: (output: AgentOutput) => Promise<void>;
|
onOutput?: (output: AgentOutput) => Promise<void>;
|
||||||
|
onTerminalStreamedOutputFlushed?: (output: AgentOutput) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTerminalStreamedOutput(output: AgentOutput): boolean {
|
||||||
|
return (output.phase ?? 'final') !== 'progress';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseLegacyAgentOutput(stdout: string): AgentOutput {
|
function parseLegacyAgentOutput(stdout: string): AgentOutput {
|
||||||
@@ -195,9 +200,16 @@ export function runSpawnedAgentProcess(
|
|||||||
'Streamed agent error output',
|
'Streamed agent error output',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
outputChain = outputChain
|
outputChain = outputChain.then(async () => {
|
||||||
.then(() => onOutput(parsed))
|
try {
|
||||||
.catch((err) => logStreamedOutputDeliveryError(err, group, input));
|
await onOutput(parsed);
|
||||||
|
if (isTerminalStreamedOutput(parsed)) {
|
||||||
|
args.onTerminalStreamedOutputFlushed?.(parsed);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logStreamedOutputDeliveryError(err, group, input);
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -235,6 +235,68 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
expect(result.newSessionId).toBe('session-456');
|
expect(result.newSessionId).toBe('session-456');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('releases Codex auth lease after final streamed output flushes even before process close', async () => {
|
||||||
|
const releaseLease = vi.fn();
|
||||||
|
const prepareGroupEnvironmentSpy = vi
|
||||||
|
.spyOn(agentRunnerEnvironment, 'prepareGroupEnvironment')
|
||||||
|
.mockReturnValueOnce({
|
||||||
|
env: {
|
||||||
|
EJCLAW_IPC_DIR: '/tmp/ejclaw-test-data/ipc/test-group',
|
||||||
|
},
|
||||||
|
groupDir: '/tmp/ejclaw-test-groups/test-group',
|
||||||
|
runnerDir: '/tmp/ejclaw-test-runners/codex-runner',
|
||||||
|
codexSessionAuth: {
|
||||||
|
canonicalAuthPath: '/tmp/codex-account/auth.json',
|
||||||
|
sessionAuthPath: '/tmp/codex-session/auth.json',
|
||||||
|
accountIndex: 0,
|
||||||
|
lease: {
|
||||||
|
accountIndex: 0,
|
||||||
|
authPath: '/tmp/codex-account/auth.json',
|
||||||
|
release: releaseLease,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const onOutput = vi.fn(async () => {});
|
||||||
|
const codexGroup: RegisteredGroup = {
|
||||||
|
...testGroup,
|
||||||
|
agentType: 'codex',
|
||||||
|
};
|
||||||
|
let resultPromise: Promise<AgentOutput> | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
resultPromise = runAgentProcess(
|
||||||
|
codexGroup,
|
||||||
|
{
|
||||||
|
...testInput,
|
||||||
|
runId: 'run-codex-lease-final',
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
onOutput,
|
||||||
|
);
|
||||||
|
|
||||||
|
emitOutputMarker(fakeProc, {
|
||||||
|
status: 'success',
|
||||||
|
result: 'TASK_DONE\n작업 완료',
|
||||||
|
phase: 'final',
|
||||||
|
newSessionId: 'codex-session-lease-final',
|
||||||
|
});
|
||||||
|
await vi.advanceTimersByTimeAsync(10);
|
||||||
|
|
||||||
|
expect(onOutput).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
result: 'TASK_DONE\n작업 완료',
|
||||||
|
phase: 'final',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(releaseLease).toHaveBeenCalledTimes(1);
|
||||||
|
} finally {
|
||||||
|
fakeProc.emit('close', 0);
|
||||||
|
await vi.advanceTimersByTimeAsync(10);
|
||||||
|
await resultPromise?.catch(() => undefined);
|
||||||
|
prepareGroupEnvironmentSpy.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('preserves streamed progress phase metadata', async () => {
|
it('preserves streamed progress phase metadata', async () => {
|
||||||
const onOutput = vi.fn(async () => {});
|
const onOutput = vi.fn(async () => {});
|
||||||
const resultPromise = runAgentProcess(
|
const resultPromise = runAgentProcess(
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ function finalizeCodexAuthSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createCodexAuthSessionFinalizer(
|
||||||
|
getAuth: () => PreparedCodexSessionAuth | null | undefined,
|
||||||
|
): () => void {
|
||||||
|
let finalized = false;
|
||||||
|
return () => {
|
||||||
|
if (finalized) return;
|
||||||
|
finalized = true;
|
||||||
|
finalizeCodexAuthSession(getAuth());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function runAgentProcess(
|
export async function runAgentProcess(
|
||||||
group: RegisteredGroup,
|
group: RegisteredGroup,
|
||||||
input: AgentInput,
|
input: AgentInput,
|
||||||
@@ -175,6 +186,9 @@ export async function runAgentProcess(
|
|||||||
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||||
const processSuffix = input.runId || `${Date.now()}`;
|
const processSuffix = input.runId || `${Date.now()}`;
|
||||||
const processName = `ejclaw-${safeName}-${processSuffix}`;
|
const processName = `ejclaw-${safeName}-${processSuffix}`;
|
||||||
|
const finalizeCodexAuthSessionOnce = createCodexAuthSessionFinalizer(
|
||||||
|
() => codexSessionAuth,
|
||||||
|
);
|
||||||
|
|
||||||
// Check if runner is built
|
// Check if runner is built
|
||||||
const distEntry = path.join(runnerDir, 'dist', 'index.js');
|
const distEntry = path.join(runnerDir, 'dist', 'index.js');
|
||||||
@@ -231,13 +245,14 @@ export async function runAgentProcess(
|
|||||||
logsDir,
|
logsDir,
|
||||||
startTime,
|
startTime,
|
||||||
onOutput,
|
onOutput,
|
||||||
|
onTerminalStreamedOutputFlushed: finalizeCodexAuthSessionOnce,
|
||||||
})
|
})
|
||||||
.then((output) => {
|
.then((output) => {
|
||||||
finalizeCodexAuthSession(codexSessionAuth);
|
finalizeCodexAuthSessionOnce();
|
||||||
resolve(output);
|
resolve(output);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
finalizeCodexAuthSession(codexSessionAuth);
|
finalizeCodexAuthSessionOnce();
|
||||||
logger.error(
|
logger.error(
|
||||||
{ err, processName, chatJid: input.chatJid, runId: input.runId },
|
{ err, processName, chatJid: input.chatJid, runId: input.runId },
|
||||||
'Spawned agent process runner failed',
|
'Spawned agent process runner failed',
|
||||||
|
|||||||
Reference in New Issue
Block a user