fix(codex): review fixes — envelope handling, mode guards, UI parity

Blocker: runCodex read raw response shapes, but the global
preSerialization hook (server.ts) wraps every payload in the
{ success, data } envelope — status.available was always undefined, so
the UI unconditionally printed "Codex CLI not found" and could never
start a session; the created session was also never auto-selected
(data.sessionId vs data.data.sessionId). Fixed both reads to match
runOpenCode, and updated the test mocks to the real wire shape (plus a
selectSession assertion) so envelope drift fails the test.

Guard parity: export isExternalCliMode() from session.ts and use it in
the ralph-config guard, all three respawn guards, and the six restore/
setup guards in server.ts that previously only excluded 'opencode' —
codex sessions could otherwise get a Ralph tracker or respawn
controller attached (idle detection is Claude-specific and output-
silence respawn cycling would misfire on a quiet codex TUI).

UI parity: cx tab badge, "Kill Tmux & Codex" dialog title, and the
missing CSS (.run-mode-dot.codex, .tab-mode.codex, .mode-codex button
colors — purple) so the Codex menu dot is no longer invisible. Removed
the dead object-literal runMode getter that Object.assign flattens
(superseded by the defineProperty accessor this PR adds).

Verified end-to-end on an isolated instance with a stub codex binary:
10/10 Playwright checks (menu/dot/label/button styling, session
created + auto-selected, cx badge, TUI output streamed, ralph+respawn
guards reject codex) and --dangerously-bypass-approvals-and-sandbox
+ --model observed on the spawned command line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-10 16:16:20 +02:00
co-authored by Claude Opus 4.8
parent 70378315da
commit 8c7a9781fa
8 changed files with 75 additions and 38 deletions
+13 -13
View File
@@ -42,7 +42,7 @@ import { execSync } from 'node:child_process';
import { hostname as getHostname } from 'node:os';
import { dataPath } from '../config/instance.js';
import { EventEmitter } from 'node:events';
import { Session, type BackgroundTask } from '../session.js';
import { Session, isExternalCliMode, type BackgroundTask } from '../session.js';
import type { ClaudeMode, SessionState } from '../types.js';
import { RespawnController, RespawnConfig } from '../respawn-controller.js';
import type { TerminalMultiplexer } from '../mux-interface.js';
@@ -1189,8 +1189,8 @@ export class WebServer extends EventEmitter {
this.runSummaryTrackers.set(session.id, summaryTracker);
summaryTracker.recordSessionStarted(session.mode, session.workingDir);
// Set working directory for Ralph tracker to auto-load @fix_plan.md (not supported for opencode sessions)
if (session.mode !== 'opencode') {
// Set working directory for Ralph tracker to auto-load @fix_plan.md (not supported for external CLIs)
if (!isExternalCliMode(session.mode)) {
session.ralphTracker.setWorkingDir(session.workingDir);
}
@@ -2016,8 +2016,8 @@ export class WebServer extends EventEmitter {
);
}
}
// Ralph / Todo tracker (not supported for opencode sessions)
if (session.mode !== 'opencode') {
// Ralph / Todo tracker (not supported for external-CLI sessions)
if (!isExternalCliMode(session.mode)) {
if (savedState.ralphAutoEnableDisabled) {
session.ralphTracker.disableAutoEnable();
console.log(`[Server] Restored Ralph auto-enable disabled for session ${session.id}`);
@@ -2046,8 +2046,8 @@ export class WebServer extends EventEmitter {
if (savedState.flickerFilterEnabled !== undefined) {
session.flickerFilterEnabled = savedState.flickerFilterEnabled;
}
// Respawn controller (not supported for opencode sessions)
if (session.mode !== 'opencode' && savedState.respawnEnabled && savedState.respawnConfig) {
// Respawn controller (not supported for external-CLI sessions)
if (!isExternalCliMode(session.mode) && savedState.respawnEnabled && savedState.respawnConfig) {
try {
this.restoreRespawnController(session, savedState.respawnConfig, 'state.json');
} catch (err) {
@@ -2056,9 +2056,9 @@ export class WebServer extends EventEmitter {
}
}
// Fallback: restore respawn from mux-sessions.json if state.json didn't have it (not supported for opencode)
// Fallback: restore respawn from mux-sessions.json if state.json didn't have it (not supported for external CLIs)
if (
session.mode !== 'opencode' &&
!isExternalCliMode(session.mode) &&
!this.respawnControllers.has(session.id) &&
muxSession.respawnConfig?.enabled
) {
@@ -2073,9 +2073,9 @@ export class WebServer extends EventEmitter {
}
// Fallback: restore Ralph state from state-inner.json if not already set and not explicitly disabled
// Ralph tracker is not supported for opencode sessions
// Ralph tracker is not supported for external-CLI sessions
if (
session.mode !== 'opencode' &&
!isExternalCliMode(session.mode) &&
!session.ralphTracker.enabled &&
!session.ralphTracker.autoEnableDisabled
) {
@@ -2086,9 +2086,9 @@ export class WebServer extends EventEmitter {
}
}
// Fallback: auto-detect completion phrase from CLAUDE.md (not supported for opencode)
// Fallback: auto-detect completion phrase from CLAUDE.md (not supported for external CLIs)
if (
session.mode !== 'opencode' &&
!isExternalCliMode(session.mode) &&
session.ralphTracker.enabled &&
!session.ralphTracker.loopState.completionPhrase
) {