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
+9 -6
View File
@@ -151,7 +151,7 @@ Object.assign(CodemanApp.prototype, {
return this.run();
},
/** Run using the selected mode (Claude Code or OpenCode) */
/** Run using the selected mode (Claude Code, OpenCode, or Codex) */
async run() {
const mode = this._runMode || 'claude';
if (mode === 'opencode') {
@@ -163,8 +163,9 @@ Object.assign(CodemanApp.prototype, {
return this.runClaude();
},
/** Get/set the run mode, persisted in localStorage */
get runMode() { return this._runMode || 'claude'; },
// Note: `runMode` is an accessor defined via Object.defineProperty at the bottom of
// this file — an object-literal getter here would be flattened to a static value by
// Object.assign (it copies values, not accessor descriptors).
setRunMode(mode) {
this._runMode = mode;
@@ -593,7 +594,7 @@ Object.assign(CodemanApp.prototype, {
try {
const statusRes = await fetch('/api/codex/status');
const status = await statusRes.json();
const status = (await statusRes.json()).data;
if (!status.available) {
this.terminal.writeln('\x1b[1;31m Codex CLI not found.\x1b[0m');
this.terminal.writeln('\x1b[90m Install with: npm install -g @openai/codex\x1b[0m');
@@ -618,8 +619,10 @@ Object.assign(CodemanApp.prototype, {
const data = await res.json();
if (!data.success) throw new Error(data.error || 'Failed to start Codex');
if (data.sessionId) {
await this.selectSession(data.sessionId);
// Switch to the new session (don't pre-set activeSessionId — selectSession
// early-returns when IDs match, skipping buffer load and sendResize)
if (data.data.sessionId) {
await this.selectSession(data.data.sessionId);
}
this.terminal.focus();