fix(codex): defer session persistence until user activity

This commit is contained in:
weishu
2026-07-12 11:00:08 +08:00
parent 65e1708c78
commit 5a377e38b9
35 changed files with 2118 additions and 131 deletions
+38 -1
View File
@@ -1,4 +1,5 @@
import { logger } from '@/ui/logger';
import { resolve } from 'node:path';
import { startHookServer } from '@/claude/utils/startHookServer';
import { codexLocal } from './codexLocal';
import type { ReasoningEffort } from './appServerTypes';
@@ -6,9 +7,10 @@ import { CodexSession } from './session';
import { createCodexSessionScanner, type CodexSessionScanner } from './utils/codexSessionScanner';
import { convertCodexEvent } from './utils/codexEventConverter';
import { buildHapiMcpBridge } from './utils/buildHapiMcpBridge';
import { stripCodexCliOverrides } from './utils/codexCliOverrides';
import { parseCodexCliOverrides, stripCodexCliOverrides } from './utils/codexCliOverrides';
import { buildCodexPermissionModeCliArgs } from './utils/permissionModeConfig';
import { BaseLocalLauncher } from '@/modules/common/launcher/BaseLocalLauncher';
import { createCodexTranscriptLocator, type CodexTranscriptLocator } from './utils/codexTranscriptLocator';
export async function codexLocalLauncher(session: CodexSession): Promise<'switch' | 'exit'> {
const resumeSessionId = session.sessionId;
@@ -18,6 +20,7 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
let hookReady = false;
let shuttingDown = false;
let pendingScannerSetup: Promise<void> | null = null;
let transcriptLocator: CodexTranscriptLocator | null = null;
const permissionMode = session.getPermissionMode();
const managedPermissionMode = permissionMode === 'read-only' || permissionMode === 'safe-yolo' || permissionMode === 'yolo'
? permissionMode
@@ -28,6 +31,8 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
...stripCodexCliOverrides(session.codexArgs)
]
: session.codexArgs;
const cwdOverride = parseCodexCliOverrides(session.codexArgs).cwd;
const effectiveCodexCwd = cwdOverride ? resolve(session.path, cwdOverride) : session.path;
// Start hapi hub for MCP bridge (same as remote mode)
const { server: happyServer, mcpServers } = await buildHapiMcpBridge(session.client);
@@ -103,6 +108,8 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
}
if (converted?.userMessage) {
session.sendUserMessage(converted.userMessage);
} else if (converted?.userActivity) {
session.notifyUserActivity();
}
if (converted?.message) {
session.sendAgentMessage(converted.message);
@@ -142,6 +149,12 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
const hookSource = typeof data.source === 'string' ? data.source : null;
const shouldAllowSessionSwitch = hookSource === 'clear';
if (transcriptPath) {
const activeLocator = transcriptLocator;
transcriptLocator = null;
void activeLocator?.cleanup();
}
if (!transcriptPath) {
handleSessionFound(sessionId, shouldAllowSessionSwitch);
return;
@@ -160,6 +173,27 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
});
logger.debug(`[codex-local]: Started Codex SessionStart hook server on port ${hookServer.port}`);
if (session.client.isPending()) {
const createdLocator = createCodexTranscriptLocator({
cwd: effectiveCodexCwd,
startupTimestampMs: Date.now(),
resumeSessionId,
onLocated: ({ sessionId, transcriptPath }) => {
if (shuttingDown || hookReady || primaryTranscriptPath) {
return;
}
transcriptLocator = null;
bindPrimarySession(sessionId, transcriptPath);
},
onAmbiguous: (paths) => {
transcriptLocator = null;
logger.warn(`[codex-local]: Transcript fallback was ambiguous (${paths.length} active candidates)`);
}
});
transcriptLocator = createdLocator;
await createdLocator.ready;
}
const launcher = new BaseLocalLauncher({
label: 'codex-local',
failureLabel: 'Local Codex process failed',
@@ -204,6 +238,9 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
shuttingDown = true;
session.removeTranscriptPathCallback(handleTranscriptPathCallback);
hookServer.stop();
const activeLocator = transcriptLocator;
transcriptLocator = null;
void activeLocator?.cleanup();
if (pendingScannerSetup) {
await pendingScannerSetup;
}