mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
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:
@@ -2572,7 +2572,7 @@ class CodemanApp {
|
||||
<span class="tab-status ${status}" aria-hidden="true"></span>
|
||||
<span class="tab-info">
|
||||
<span class="tab-name-row">
|
||||
${mode === 'shell' ? '<span class="tab-mode shell" aria-hidden="true">sh</span>' : mode === 'opencode' ? '<span class="tab-mode opencode" aria-hidden="true">oc</span>' : ''}
|
||||
${mode === 'shell' ? '<span class="tab-mode shell" aria-hidden="true">sh</span>' : mode === 'opencode' ? '<span class="tab-mode opencode" aria-hidden="true">oc</span>' : mode === 'codex' ? '<span class="tab-mode codex" aria-hidden="true">cx</span>' : ''}
|
||||
<span class="tab-name" data-session-id="${id}">${(() => { const p = parseSessionPrefix(name); return p && p.suffix ? '<span class="tab-prefix">' + escapeHtml(p.prefix) + '</span><span class="tab-suffix">: ' + escapeHtml(p.suffix) + '</span>' : escapeHtml(name); })()}</span>
|
||||
<span class="tab-detached-badge" aria-hidden="true">detached</span>
|
||||
</span>
|
||||
@@ -3374,7 +3374,9 @@ class CodemanApp {
|
||||
if (killTitle) {
|
||||
killTitle.textContent = session.mode === 'opencode'
|
||||
? 'Kill Tmux & OpenCode'
|
||||
: 'Kill Tmux & Claude Code';
|
||||
: session.mode === 'codex'
|
||||
? 'Kill Tmux & Codex'
|
||||
: 'Kill Tmux & Claude Code';
|
||||
}
|
||||
|
||||
document.getElementById('closeConfirmModal').classList.add('active');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1064,6 +1064,11 @@ body.solo-mode .btn-lifecycle-log {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.session-tab .tab-mode.codex {
|
||||
background: rgba(168, 85, 247, 0.2);
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
/* Timer Banner - Compact */
|
||||
.timer-banner {
|
||||
display: flex;
|
||||
@@ -2745,6 +2750,22 @@ body.solo-mode .btn-lifecycle-log {
|
||||
color: #a7f3d0;
|
||||
}
|
||||
|
||||
/* Codex mode colors */
|
||||
.btn-toolbar.btn-run.mode-codex,
|
||||
.btn-toolbar.btn-run-gear.mode-codex {
|
||||
background: linear-gradient(135deg, #2a0a3e 0%, #350b4d 50%, #400d5e 100%);
|
||||
border-color: rgba(168, 85, 247, 0.5);
|
||||
color: #d8b4fe;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.btn-toolbar.btn-run.mode-codex:hover,
|
||||
.btn-toolbar.btn-run-gear.mode-codex:hover {
|
||||
background: linear-gradient(135deg, #400d5e 0%, #581c87 50%, #6b21a8 100%);
|
||||
box-shadow: 0 0 12px rgba(168, 85, 247, 0.35), 0 2px 8px rgba(168, 85, 247, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(192, 132, 252, 0.6);
|
||||
color: #e9d5ff;
|
||||
}
|
||||
|
||||
/* Dropdown menu */
|
||||
.run-mode-menu {
|
||||
display: none;
|
||||
@@ -2797,6 +2818,7 @@ body.solo-mode .btn-lifecycle-log {
|
||||
}
|
||||
.run-mode-dot.claude { background: #3b82f6; }
|
||||
.run-mode-dot.opencode { background: #10b981; }
|
||||
.run-mode-dot.codex { background: #a855f7; }
|
||||
|
||||
.run-mode-sep {
|
||||
height: 1px;
|
||||
|
||||
Reference in New Issue
Block a user