mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(codex): defer session persistence until user activity
This commit is contained in:
@@ -70,11 +70,13 @@ function createSessionStub(
|
||||
codexArgs?: string[],
|
||||
path = '/tmp/worktree',
|
||||
initialTranscriptPath: string | null = null,
|
||||
replayTranscriptHistoryOnStart = false
|
||||
replayTranscriptHistoryOnStart = false,
|
||||
pendingClient = false
|
||||
) {
|
||||
const sessionEvents: Array<{ type: string; message?: string }> = [];
|
||||
const userMessages: string[] = [];
|
||||
const agentMessages: unknown[] = [];
|
||||
let userActivityCount = 0;
|
||||
let localLaunchFailure: { message: string; exitReason: 'switch' | 'exit' } | null = null;
|
||||
let sessionId: string | null = null;
|
||||
let transcriptPath: string | null = initialTranscriptPath;
|
||||
@@ -94,6 +96,7 @@ function createSessionStub(
|
||||
codexArgs,
|
||||
replayTranscriptHistoryOnStart,
|
||||
client: {
|
||||
isPending: () => pendingClient,
|
||||
rpcHandlerManager: {
|
||||
registerHandler: () => {}
|
||||
}
|
||||
@@ -130,6 +133,9 @@ function createSessionStub(
|
||||
sendUserMessage: (message: string) => {
|
||||
userMessages.push(message);
|
||||
},
|
||||
notifyUserActivity: () => {
|
||||
userActivityCount += 1;
|
||||
},
|
||||
sendAgentMessage: (message: unknown) => {
|
||||
agentMessages.push(message);
|
||||
},
|
||||
@@ -138,6 +144,7 @@ function createSessionStub(
|
||||
sessionEvents,
|
||||
userMessages,
|
||||
agentMessages,
|
||||
getUserActivityCount: () => userActivityCount,
|
||||
getLocalLaunchFailure: () => localLaunchFailure
|
||||
};
|
||||
}
|
||||
@@ -354,6 +361,68 @@ describe('codexLocalLauncher', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to fresh transcript activity when SessionStart does not arrive', async () => {
|
||||
const originalCodexHome = process.env.CODEX_HOME;
|
||||
process.env.CODEX_HOME = tempDir;
|
||||
const now = new Date();
|
||||
const sessionDirectory = join(
|
||||
tempDir,
|
||||
'sessions',
|
||||
String(now.getUTCFullYear()),
|
||||
String(now.getUTCMonth() + 1).padStart(2, '0'),
|
||||
String(now.getUTCDate()).padStart(2, '0')
|
||||
);
|
||||
await mkdir(sessionDirectory, { recursive: true });
|
||||
const transcriptPath = join(sessionDirectory, 'rollout-fallback-thread.jsonl');
|
||||
const { session, userMessages } = createSessionStub(
|
||||
'default',
|
||||
['--cd', '/tmp/effective-codex-cwd'],
|
||||
'/tmp/worktree',
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
let releaseRunBarrier: (() => void) | undefined;
|
||||
harness.runBarrier = new Promise((resolve) => {
|
||||
releaseRunBarrier = resolve;
|
||||
});
|
||||
|
||||
try {
|
||||
const launcherPromise = codexLocalLauncher(session as never);
|
||||
await vi.waitFor(() => expect(harness.launches).toHaveLength(1));
|
||||
expect(session.sessionId).toBeNull();
|
||||
|
||||
await writeFile(transcriptPath, [
|
||||
JSON.stringify({
|
||||
type: 'session_meta',
|
||||
payload: { id: 'fallback-thread', cwd: '/tmp/effective-codex-cwd' }
|
||||
}),
|
||||
JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'event_msg',
|
||||
payload: { type: 'user_message', message: 'fallback prompt' }
|
||||
})
|
||||
].join('\n') + '\n');
|
||||
|
||||
await vi.waitFor(
|
||||
() => expect(session.sessionId).toBe('fallback-thread'),
|
||||
{ timeout: 3_000, interval: 50 }
|
||||
);
|
||||
if (releaseRunBarrier) releaseRunBarrier();
|
||||
await launcherPromise;
|
||||
|
||||
expect(session.transcriptPath).toBe(transcriptPath);
|
||||
expect(userMessages).toContain('fallback prompt');
|
||||
} finally {
|
||||
if (releaseRunBarrier) releaseRunBarrier();
|
||||
if (originalCodexHome === undefined) {
|
||||
delete process.env.CODEX_HOME;
|
||||
} else {
|
||||
process.env.CODEX_HOME = originalCodexHome;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('replays existing transcript messages when importing a Codex thread into a new Hapi session', async () => {
|
||||
const transcriptPath = join(tempDir, 'codex-import-transcript.jsonl');
|
||||
const { session, agentMessages } = createSessionStub('default', undefined, '/tmp/worktree', null, true);
|
||||
@@ -392,7 +461,7 @@ describe('codexLocalLauncher', () => {
|
||||
|
||||
it('replays existing response_item chat messages when importing a Codex thread into a new Hapi session', async () => {
|
||||
const transcriptPath = join(tempDir, 'codex-import-response-item-transcript.jsonl');
|
||||
const { session, userMessages, agentMessages } = createSessionStub('default', undefined, '/tmp/worktree', null, true);
|
||||
const { session, userMessages, agentMessages, getUserActivityCount } = createSessionStub('default', undefined, '/tmp/worktree', null, true);
|
||||
let releaseRunBarrier: (() => void) | undefined;
|
||||
harness.runBarrier = new Promise((resolve) => {
|
||||
releaseRunBarrier = resolve;
|
||||
@@ -417,6 +486,14 @@ describe('codexLocalLauncher', () => {
|
||||
role: 'assistant',
|
||||
content: [{ type: 'output_text', text: 'old response_item assistant message' }]
|
||||
}
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'response_item',
|
||||
payload: {
|
||||
type: 'message',
|
||||
role: 'user',
|
||||
content: [{ type: 'input_image', image_url: 'data:image/png;base64,abc' }]
|
||||
}
|
||||
})
|
||||
].join('\n') + '\n'
|
||||
);
|
||||
@@ -435,6 +512,7 @@ describe('codexLocalLauncher', () => {
|
||||
await launcherPromise;
|
||||
|
||||
expect(userMessages).toContain('old response_item user message');
|
||||
expect(getUserActivityCount()).toBe(1);
|
||||
expect(agentMessages).toContainEqual({
|
||||
type: 'message',
|
||||
message: 'old response_item assistant message',
|
||||
|
||||
Reference in New Issue
Block a user