feat(codex): share sessions across terminal and web

Use one native app-server for terminal, Web and phone clients while retaining the existing CLI and Runner lifecycle.

Synchronize native queues, permissions, question history and steering state; preserve explicit permission precedence and per-turn usage models. Resume inactive clear commands through Runner and reject independent child cold resumes.

Add shared-runtime regression tests, generated protocol fixtures and lifecycle documentation.
This commit is contained in:
weishu
2026-09-12 10:59:17 +08:00
parent 25af3f8e13
commit 0c4abcb3d1
111 changed files with 4684 additions and 960 deletions
+1 -63
View File
@@ -1,11 +1,4 @@
import { MessageQueue2 } from '@/utils/MessageQueue2';
import { logger } from '@/ui/logger';
import { runLocalRemoteSession } from '@/agent/loopBase';
import { CodexSession } from './session';
import { codexLocalLauncher } from './codexLocalLauncher';
import { codexRemoteLauncher } from './codexRemoteLauncher';
import { ApiClient, ApiSessionClient } from '@/lib';
import type { CodexCliOverrides } from './utils/codexCliOverrides';
// Mode data retained for protocol/config helpers. Codex execution is shared/runtime.ts.
import type { ReasoningEffort } from './appServerTypes';
import type { CodexCollaborationMode, CodexPermissionMode } from '@hapi/protocol/types';
@@ -28,58 +21,3 @@ export interface EnhancedMode {
/** When set, forwarded to app-server thread/turn params. */
personality?: CodexPersonality;
}
interface LoopOptions {
path: string;
startingMode?: 'local' | 'remote';
startedBy?: 'runner' | 'terminal';
onModeChange: (mode: 'local' | 'remote') => void;
messageQueue: MessageQueue2<EnhancedMode>;
session: ApiSessionClient;
api: ApiClient;
codexArgs?: string[];
codexCliOverrides?: CodexCliOverrides;
permissionMode?: PermissionMode;
model?: string;
modelReasoningEffort?: ReasoningEffort;
collaborationMode?: CodexCollaborationMode;
resumeSessionId?: string;
sourceSessionId?: string;
replayTranscriptHistoryOnStart?: boolean;
onSessionReady?: (session: CodexSession) => void;
}
export async function loop(opts: LoopOptions): Promise<void> {
const logPath = logger.getLogPath();
const startedBy = opts.startedBy ?? 'terminal';
const startingMode = opts.startingMode ?? 'local';
const session = new CodexSession({
api: opts.api,
client: opts.session,
path: opts.path,
sessionId: opts.resumeSessionId ?? null,
logPath,
messageQueue: opts.messageQueue,
onModeChange: opts.onModeChange,
mode: startingMode,
startedBy,
startingMode,
codexArgs: opts.codexArgs,
codexCliOverrides: opts.codexCliOverrides,
permissionMode: opts.permissionMode ?? 'default',
model: opts.model,
modelReasoningEffort: opts.modelReasoningEffort,
collaborationMode: opts.collaborationMode ?? 'default',
sourceSessionId: opts.sourceSessionId,
replayTranscriptHistoryOnStart: opts.replayTranscriptHistoryOnStart ?? false
});
await runLocalRemoteSession({
session,
startingMode: opts.startingMode,
logTag: 'codex-loop',
runLocal: codexLocalLauncher,
runRemote: codexRemoteLauncher,
onSessionReady: opts.onSessionReady
});
}