mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
feat(codex): add Codex (OpenAI CLI) run-mode foundation
Add Codex as a first-class session mode alongside Claude/Shell/OpenCode. - codex-cli-resolver: locate the `codex` binary and augment PATH (mirrors the OpenCode resolver) - SessionMode 'codex' + CodexConfig (model, resumeSessionId, dangerouslyBypass, renderMode); persisted in SessionState and threaded through CreateSession/ RespawnPane options - schema validation: CodexConfigSchema, CODEX_ env-var prefix allowlist, mode enums on create/quick-start, codexDangerouslyBypassApprovals setting - tmux launch: buildCodexCommand, setenv for OPENAI_API_KEY/CODEX_* (keeps secrets out of ps), truecolor COLORTERM, codex PATH resolution - session + routes: availability check (clear install hint), config passthrough, tmux-required guard; Codex skips Claude-only parsers (Ralph/respawn/token) - run-mode UI: "Run CX" selector option + dedicated Codex CLI settings tab with the bypass-approvals toggle; GET /api/codex/status Scope: foundation only. Codex terminal redraw handling and xterm snapshot/replay are intentionally excluded and tracked separately. Verification: tsc --noEmit, eslint, prettier --check, check:frontend-syntax all clean; full test:ci suite green (2712 passed, 0 failed); server boot smoke OK. Co-Authored-By: Saqeb Akhter <saqeb.akhter@gmail.com>
This commit is contained in:
co-authored by
Saqeb Akhter
parent
e1f611b8fb
commit
70378315da
+33
-6
@@ -46,6 +46,7 @@ import {
|
||||
type ClaudeMode,
|
||||
type SessionMode,
|
||||
type OpenCodeConfig,
|
||||
type CodexConfig,
|
||||
type EffortLevel,
|
||||
} from './types.js';
|
||||
import type { TerminalMultiplexer, MuxSession } from './mux-interface.js';
|
||||
@@ -123,6 +124,11 @@ const CTRL_L_PATTERN = /\x0c/g;
|
||||
/** Pattern to split by newlines (CR or LF) */
|
||||
const NEWLINE_SPLIT_PATTERN = /\r?\n/;
|
||||
|
||||
/** True for external-CLI run modes (non-Claude) that use their own TUI and output format. */
|
||||
function isExternalCliMode(mode: SessionMode): boolean {
|
||||
return mode === 'opencode' || mode === 'codex';
|
||||
}
|
||||
|
||||
// Note: Claude CLI PATH resolution moved to session-cli-builder.ts (buildClaudeEnv)
|
||||
|
||||
/** PTY fallback geometry when tmux can't be queried (matches pre-#80 hardcoded values). */
|
||||
@@ -313,6 +319,8 @@ export class Session extends EventEmitter {
|
||||
|
||||
// OpenCode configuration (only for mode === 'opencode')
|
||||
private _openCodeConfig: OpenCodeConfig | undefined;
|
||||
// Codex configuration (only for mode === 'codex')
|
||||
private _codexConfig: CodexConfig | undefined;
|
||||
private _resumeSessionId: string | undefined;
|
||||
|
||||
// Ephemeral env overrides (e.g., CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS). Exported by tmux
|
||||
@@ -381,6 +389,8 @@ export class Session extends EventEmitter {
|
||||
allowedTools?: string;
|
||||
/** OpenCode configuration (only for mode === 'opencode') */
|
||||
openCodeConfig?: OpenCodeConfig;
|
||||
/** Codex configuration (only for mode === 'codex') */
|
||||
codexConfig?: CodexConfig;
|
||||
/** Resume a previous Claude conversation (used after server reboot) */
|
||||
resumeSessionId?: string;
|
||||
/** Extra env vars exported to the CLI at spawn time (no disk persistence) */
|
||||
@@ -434,6 +444,11 @@ export class Session extends EventEmitter {
|
||||
this._openCodeConfig = config.openCodeConfig;
|
||||
}
|
||||
|
||||
// Apply Codex configuration
|
||||
if (config.codexConfig) {
|
||||
this._codexConfig = config.codexConfig;
|
||||
}
|
||||
|
||||
// Apply env overrides (exported at spawn, not persisted to disk).
|
||||
// Legacy migration: pre-0.7.2 carried effort as the CLAUDE_CODE_EFFORT_LEVEL env var,
|
||||
// which hard-locks /effort switching. Extract it into _effort (--settings soft default)
|
||||
@@ -696,6 +711,11 @@ export class Session extends EventEmitter {
|
||||
return this._allowedTools;
|
||||
}
|
||||
|
||||
/** Codex CLI configuration for this session. */
|
||||
get codexConfig(): CodexConfig | undefined {
|
||||
return this._codexConfig;
|
||||
}
|
||||
|
||||
// Note: _buildPermissionArgs removed — now using buildInteractiveArgs from session-cli-builder.ts
|
||||
|
||||
/**
|
||||
@@ -867,6 +887,7 @@ export class Session extends EventEmitter {
|
||||
cliAccountType: this._cliAccountType || undefined,
|
||||
cliLatestVersion: this._cliLatestVersion || undefined,
|
||||
openCodeConfig: this._openCodeConfig,
|
||||
codexConfig: this._codexConfig,
|
||||
resumeSessionId: this._resumeSessionId,
|
||||
effort: this._effort,
|
||||
// envOverrides intentionally NOT on the public SessionState type — they must not
|
||||
@@ -1045,7 +1066,7 @@ export class Session extends EventEmitter {
|
||||
|
||||
this._resetBuffers();
|
||||
|
||||
const modeLabel = this.mode === 'opencode' ? 'OpenCode' : 'Claude';
|
||||
const modeLabel = this.mode === 'opencode' ? 'OpenCode' : this.mode === 'codex' ? 'Codex' : 'Claude';
|
||||
console.log(
|
||||
`[Session] Starting interactive ${modeLabel} session` + (this._useMux ? ` (with ${this._mux!.backend})` : '')
|
||||
);
|
||||
@@ -1063,6 +1084,7 @@ export class Session extends EventEmitter {
|
||||
claudeMode: this._claudeMode,
|
||||
allowedTools: this._allowedTools,
|
||||
openCodeConfig: this._openCodeConfig,
|
||||
codexConfig: this._codexConfig,
|
||||
resumeSessionId: this._resumeSessionId,
|
||||
envOverrides: this._envOverrides,
|
||||
effort: this._effort,
|
||||
@@ -1077,6 +1099,7 @@ export class Session extends EventEmitter {
|
||||
claudeMode: this._claudeMode,
|
||||
allowedTools: this._allowedTools,
|
||||
openCodeConfig: this._openCodeConfig,
|
||||
codexConfig: this._codexConfig,
|
||||
resumeSessionId: this._resumeSessionId,
|
||||
envOverrides: this._envOverrides,
|
||||
effort: this._effort,
|
||||
@@ -1090,8 +1113,8 @@ export class Session extends EventEmitter {
|
||||
// For NEW mux sessions: wait for readiness then clean buffer
|
||||
// For RESTORED mux sessions: don't do anything - client will fetch buffer on tab switch
|
||||
if (!isRestored) {
|
||||
if (this.mode === 'opencode') {
|
||||
// OpenCode uses Bubble Tea TUI — no ❯ prompt to detect.
|
||||
if (isExternalCliMode(this.mode)) {
|
||||
// External CLIs use custom TUIs — no ❯ prompt to detect.
|
||||
// Wait for TUI to stabilize (output stops changing), then mark ready.
|
||||
// Don't clear the buffer — the TUI's initial render IS the useful content.
|
||||
// Emit needsRefresh so the client fetches the full buffer once the TUI has rendered.
|
||||
@@ -1146,6 +1169,10 @@ export class Session extends EventEmitter {
|
||||
if (this.mode === 'opencode') {
|
||||
throw new Error('OpenCode sessions require tmux. Direct PTY fallback is not supported.');
|
||||
}
|
||||
// Codex sessions require tmux for OPENAI_API_KEY injection via setenv
|
||||
if (this.mode === 'codex') {
|
||||
throw new Error('Codex sessions require tmux. Direct PTY fallback is not supported.');
|
||||
}
|
||||
try {
|
||||
// Pass --session-id to use the SAME ID as the Codeman session
|
||||
// This ensures subagents can be directly matched to the correct tab
|
||||
@@ -1305,9 +1332,9 @@ export class Session extends EventEmitter {
|
||||
* PTY data chunk. Receives accumulated raw data to process in one batch.
|
||||
*/
|
||||
private _processExpensiveParsers(rawData: string): void {
|
||||
// Skip Claude-specific parsers for OpenCode sessions — Ralph tracker, BashToolParser,
|
||||
// token parsing, and CLI info parsing all depend on Claude's output format.
|
||||
if (this.mode === 'opencode') return;
|
||||
// Skip Claude-specific parsers for external CLI sessions (Ralph tracker,
|
||||
// BashToolParser, token + CLI-info parsing all depend on Claude's output format).
|
||||
if (isExternalCliMode(this.mode)) return;
|
||||
|
||||
// Lazy ANSI strip: only compute cleanData when a consumer actually needs it.
|
||||
let _cleanData: string | null = null;
|
||||
|
||||
Reference in New Issue
Block a user