mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
Merge pull request #114 from aakhter/pr/cod-34-codex
feat(codex): add Codex (OpenAI CLI) run-mode foundation
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');
|
||||
|
||||
@@ -384,6 +384,9 @@
|
||||
<button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
|
||||
<span class="run-mode-dot opencode"></span>OpenCode
|
||||
</button>
|
||||
<button class="run-mode-option" data-mode="codex" onclick="app.setRunMode('codex')">
|
||||
<span class="run-mode-dot codex"></span>Codex
|
||||
</button>
|
||||
<div class="run-mode-sep"></div>
|
||||
<div class="run-mode-header">Recent Sessions</div>
|
||||
<div class="run-mode-history" id="runModeHistory"></div>
|
||||
@@ -895,6 +898,7 @@
|
||||
<div class="modal-tabs">
|
||||
<button class="modal-tab-btn active" data-tab="settings-display">Display</button>
|
||||
<button class="modal-tab-btn" data-tab="settings-claude">Claude CLI</button>
|
||||
<button class="modal-tab-btn" data-tab="settings-codex">Codex CLI</button>
|
||||
<button class="modal-tab-btn" data-tab="settings-models">Models</button>
|
||||
<button class="modal-tab-btn" data-tab="settings-paths">Paths</button>
|
||||
<button class="modal-tab-btn" data-tab="settings-notifications">Notifications</button>
|
||||
@@ -1181,6 +1185,18 @@
|
||||
<span class="form-hint">Process priority (-20 to 19, higher = lower priority, default: 10)</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Codex CLI Tab -->
|
||||
<div class="modal-tab-content hidden" id="settings-codex">
|
||||
<div class="form-section-header">Codex CLI</div>
|
||||
<div class="form-row form-row-switch">
|
||||
<label>Bypass Approvals and Sandbox</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="appSettingsCodexDangerouslyBypassApprovals">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Models Tab -->
|
||||
<div class="modal-tab-content hidden" id="settings-models">
|
||||
<div class="form-row">
|
||||
|
||||
@@ -151,17 +151,21 @@ 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') {
|
||||
return this.runOpenCode();
|
||||
}
|
||||
if (mode === 'codex') {
|
||||
return this.runCodex();
|
||||
}
|
||||
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;
|
||||
@@ -253,7 +257,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
gearBtn.className = `btn-toolbar btn-run-gear mode-${mode}`;
|
||||
}
|
||||
if (label) {
|
||||
label.textContent = mode === 'opencode' ? 'Run OC' : 'Run';
|
||||
label.textContent = mode === 'opencode' ? 'Run OC' : mode === 'codex' ? 'Run CX' : 'Run';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -580,6 +584,53 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
async runCodex() {
|
||||
const caseName = document.getElementById('quickStartCase').value || 'testcase';
|
||||
|
||||
this.terminal.clear();
|
||||
this.terminal.writeln(`\x1b[1;32m Starting Codex session in ${caseName}...\x1b[0m`);
|
||||
this.terminal.writeln('');
|
||||
this.terminal.focus();
|
||||
|
||||
try {
|
||||
const statusRes = await fetch('/api/codex/status');
|
||||
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');
|
||||
return;
|
||||
}
|
||||
|
||||
const globalSettings = this.loadAppSettingsFromStorage();
|
||||
const envOverrides = this.buildEnvOverrides(this.getCaseSettings(caseName), globalSettings);
|
||||
const res = await fetch('/api/quick-start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
caseName,
|
||||
mode: 'codex',
|
||||
codexConfig: {
|
||||
dangerouslyBypassApprovals: globalSettings.codexDangerouslyBypassApprovals ?? false,
|
||||
renderMode: 'hybrid',
|
||||
},
|
||||
...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}),
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.success) throw new Error(data.error || 'Failed to start Codex');
|
||||
|
||||
// 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();
|
||||
} catch (err) {
|
||||
this.terminal.writeln(`\x1b[1;31m Error: ${err.message}\x1b[0m`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Session Options Modal
|
||||
@@ -592,7 +643,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.editingSessionId = sessionId;
|
||||
|
||||
// Reset to an appropriate tab — Summary for OpenCode (Respawn/Ralph are Claude-only)
|
||||
this.switchOptionsTab(session.mode === 'opencode' ? 'summary' : 'respawn');
|
||||
this.switchOptionsTab(session.mode === 'opencode' || session.mode === 'codex' ? 'summary' : 'respawn');
|
||||
|
||||
// Update respawn status display and buttons
|
||||
const respawnStatus = document.getElementById('sessionRespawnStatus');
|
||||
@@ -621,7 +672,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
|
||||
// Hide Claude-specific options for OpenCode sessions
|
||||
const isOpenCode = session.mode === 'opencode';
|
||||
const isOpenCode = session.mode === 'opencode' || session.mode === 'codex';
|
||||
const claudeOnlyEls = document.querySelectorAll('[data-claude-only]');
|
||||
claudeOnlyEls.forEach(el => { el.style.display = isOpenCode ? 'none' : ''; });
|
||||
|
||||
@@ -1474,3 +1525,14 @@ Object.assign(CodemanApp.prototype, {
|
||||
setTimeout(() => modal.classList.remove('from-mobile'), 300);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(CodemanApp.prototype, 'runMode', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this._runMode || 'claude';
|
||||
},
|
||||
set(mode) {
|
||||
this._runMode = mode === 'opencode' || mode === 'codex' || mode === 'claude' ? mode : 'claude';
|
||||
},
|
||||
});
|
||||
|
||||
@@ -340,6 +340,9 @@ Object.assign(CodemanApp.prototype, {
|
||||
claudeModeSelect.onchange = () => {
|
||||
allowedToolsRow.style.display = claudeModeSelect.value === 'allowedTools' ? '' : 'none';
|
||||
};
|
||||
// Codex CLI settings
|
||||
document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked =
|
||||
settings.codexDangerouslyBypassApprovals ?? false;
|
||||
// Claude Permissions settings
|
||||
document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false;
|
||||
document.getElementById('appSettingsOpusContext1m').checked = settings.opusContext1mEnabled ?? false;
|
||||
@@ -1342,6 +1345,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Claude CLI settings
|
||||
claudeMode: document.getElementById('appSettingsClaudeMode').value,
|
||||
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
|
||||
// Codex CLI settings
|
||||
codexDangerouslyBypassApprovals: document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked,
|
||||
// Claude Permissions settings
|
||||
agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked,
|
||||
opusContext1mEnabled: document.getElementById('appSettingsOpusContext1m').checked,
|
||||
|
||||
@@ -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