Merge pull request #181 from Lint111/agent/split-codex-animations

feat(codex): make terminal animations configurable
This commit is contained in:
Ark0N
2026-08-05 00:20:38 +02:00
committed by GitHub
8 changed files with 33 additions and 1 deletions
+4
View File
@@ -643,6 +643,10 @@ export function buildCodexCommand(config?: CodexConfig): string {
parts.push('--dangerously-bypass-approvals-and-sandbox'); parts.push('--dangerously-bypass-approvals-and-sandbox');
} }
if (config?.animations !== undefined) {
parts.push('--config', `tui.animations=${config.animations ? 'true' : 'false'}`);
}
if (config?.model) { if (config?.model) {
const safeModel = /^[a-zA-Z0-9._\-/]+$/.test(config.model) ? config.model : undefined; const safeModel = /^[a-zA-Z0-9._\-/]+$/.test(config.model) ? config.model : undefined;
if (safeModel) parts.push('--model', safeModel); if (safeModel) parts.push('--model', safeModel);
+2
View File
@@ -305,6 +305,8 @@ export interface CodexConfig {
resumeSessionId?: string; resumeSessionId?: string;
/** Bypass approval prompts (passes --dangerously-bypass-approvals-and-sandbox) */ /** Bypass approval prompts (passes --dangerously-bypass-approvals-and-sandbox) */
dangerouslyBypassApprovals?: boolean; dangerouslyBypassApprovals?: boolean;
/** Enable Codex's decorative TUI animations. Disable to reduce remote terminal redraws. */
animations?: boolean;
/** Browser rendering strategy for Codex sessions. Hybrid TUI is the only supported mode. */ /** Browser rendering strategy for Codex sessions. Hybrid TUI is the only supported mode. */
renderMode?: CodexRenderMode; renderMode?: CodexRenderMode;
} }
+8
View File
@@ -1688,6 +1688,14 @@
</label> </label>
<span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span> <span class="form-hint">Start new Codex sessions with --dangerously-bypass-approvals-and-sandbox</span>
</div> </div>
<div class="form-row form-row-switch">
<label>Animated Status Effects</label>
<label class="switch">
<input type="checkbox" id="appSettingsCodexAnimations">
<span class="slider"></span>
</label>
<span class="form-hint">Decorative Codex TUI motion for new local sessions. Leave off to reduce remote and mobile redraws.</span>
</div>
</div> </div>
<!-- Models Tab --> <!-- Models Tab -->
<div class="modal-tab-content hidden" id="settings-models"> <div class="modal-tab-content hidden" id="settings-models">
+1
View File
@@ -1040,6 +1040,7 @@ Object.assign(CodemanApp.prototype, {
...(isRemote ? {} : { ...(isRemote ? {} : {
codexConfig: { codexConfig: {
dangerouslyBypassApprovals: globalSettings.codexDangerouslyBypassApprovals ?? false, dangerouslyBypassApprovals: globalSettings.codexDangerouslyBypassApprovals ?? false,
animations: globalSettings.codexAnimationsEnabled ?? false,
renderMode: 'hybrid', renderMode: 'hybrid',
}, },
...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}), ...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}),
+3
View File
@@ -376,6 +376,8 @@ Object.assign(CodemanApp.prototype, {
// Codex CLI settings // Codex CLI settings
document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked = document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked =
settings.codexDangerouslyBypassApprovals ?? false; settings.codexDangerouslyBypassApprovals ?? false;
document.getElementById('appSettingsCodexAnimations').checked =
settings.codexAnimationsEnabled ?? false;
// Claude Permissions settings // Claude Permissions settings
document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false; document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false;
document.getElementById('appSettingsClaudeModel').value = settings.claudeModel ?? ''; document.getElementById('appSettingsClaudeModel').value = settings.claudeModel ?? '';
@@ -1482,6 +1484,7 @@ Object.assign(CodemanApp.prototype, {
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(), allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
// Codex CLI settings // Codex CLI settings
codexDangerouslyBypassApprovals: document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked, codexDangerouslyBypassApprovals: document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked,
codexAnimationsEnabled: document.getElementById('appSettingsCodexAnimations').checked,
// Claude Permissions settings // Claude Permissions settings
agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked, agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked,
claudeModel: document.getElementById('appSettingsClaudeModel').value, claudeModel: document.getElementById('appSettingsClaudeModel').value,
+2
View File
@@ -182,6 +182,7 @@ const CodexConfigSchema = z
.regex(/^[a-zA-Z0-9_-]+$/) .regex(/^[a-zA-Z0-9_-]+$/)
.optional(), .optional(),
dangerouslyBypassApprovals: z.boolean().optional(), dangerouslyBypassApprovals: z.boolean().optional(),
animations: z.boolean().optional(),
renderMode: z renderMode: z
.enum(['scrollback', 'hybrid']) .enum(['scrollback', 'hybrid'])
.optional() .optional()
@@ -776,6 +777,7 @@ export const SettingsUpdateSchema = z
allowedTools: z.string().max(2000).optional(), allowedTools: z.string().max(2000).optional(),
// Codex CLI settings // Codex CLI settings
codexDangerouslyBypassApprovals: z.boolean().optional(), codexDangerouslyBypassApprovals: z.boolean().optional(),
codexAnimationsEnabled: z.boolean().optional(),
// Terminal history and retention // Terminal history and retention
terminalScrollbackLines: z terminalScrollbackLines: z
.number() .number()
+4 -1
View File
@@ -283,11 +283,13 @@ describe('Codex quick start settings', () => {
/<div class="modal-tab-content hidden" id="settings-claude">([\s\S]*?)<!-- Codex CLI Tab -->/ /<div class="modal-tab-content hidden" id="settings-claude">([\s\S]*?)<!-- Codex CLI Tab -->/
); );
expect(claudeTab?.[1]).not.toContain('appSettingsCodexDangerouslyBypassApprovals'); expect(claudeTab?.[1]).not.toContain('appSettingsCodexDangerouslyBypassApprovals');
expect(claudeTab?.[1]).not.toContain('appSettingsCodexAnimations');
const codexTab = html.match( const codexTab = html.match(
/<div class="modal-tab-content hidden" id="settings-codex">([\s\S]*?)<\/div>\s*<!-- Models Tab -->/ /<div class="modal-tab-content hidden" id="settings-codex">([\s\S]*?)<\/div>\s*<!-- Models Tab -->/
); );
expect(codexTab?.[1]).toContain('appSettingsCodexDangerouslyBypassApprovals'); expect(codexTab?.[1]).toContain('appSettingsCodexDangerouslyBypassApprovals');
expect(codexTab?.[1]).toContain('appSettingsCodexAnimations');
expect(codexTab?.[1]).not.toContain('appSettingsCodexRenderMode'); expect(codexTab?.[1]).not.toContain('appSettingsCodexRenderMode');
}); });
@@ -327,6 +329,7 @@ describe('Codex quick start settings', () => {
app.terminal = { clear: () => {}, writeln: () => {}, focus: () => {} }; app.terminal = { clear: () => {}, writeln: () => {}, focus: () => {} };
app.loadAppSettingsFromStorage = () => ({ app.loadAppSettingsFromStorage = () => ({
codexDangerouslyBypassApprovals: true, codexDangerouslyBypassApprovals: true,
codexAnimationsEnabled: false,
}); });
app.getCaseSettings = () => ({}); app.getCaseSettings = () => ({});
app.buildEnvOverrides = () => ({}); app.buildEnvOverrides = () => ({});
@@ -345,7 +348,7 @@ describe('Codex quick start settings', () => {
mode: 'codex', mode: 'codex',
// tabs follow the w<n>-<case> naming convention (quick-start would otherwise auto-name codeman-<id>) // tabs follow the w<n>-<case> naming convention (quick-start would otherwise auto-name codeman-<id>)
sessionName: 'w1-codex-case', sessionName: 'w1-codex-case',
codexConfig: { dangerouslyBypassApprovals: true, renderMode: 'hybrid' }, codexConfig: { dangerouslyBypassApprovals: true, animations: false, renderMode: 'hybrid' },
}); });
expect(selected).toEqual(['sess-1']); expect(selected).toEqual(['sess-1']);
}); });
+9
View File
@@ -10,6 +10,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { import {
TmuxManager, TmuxManager,
buildCodexCommand,
buildRemoteKillCommand, buildRemoteKillCommand,
buildRemoteLaunchCommand, buildRemoteLaunchCommand,
formatPaneSnapshot, formatPaneSnapshot,
@@ -98,6 +99,14 @@ describe('TmuxManager (unit)', () => {
}); });
}); });
describe('Codex command builder', () => {
it('controls decorative TUI animation through Codex config', () => {
expect(buildCodexCommand({ animations: false })).toBe('codex --config tui.animations=false');
expect(buildCodexCommand({ animations: true })).toBe('codex --config tui.animations=true');
expect(buildCodexCommand()).toBe('codex');
});
});
describe('remote launch command builder', () => { describe('remote launch command builder', () => {
it('wraps codex command overrides in ssh with remote tmux launch', () => { it('wraps codex command overrides in ssh with remote tmux launch', () => {
const command = buildRemoteLaunchCommand({ const command = buildRemoteLaunchCommand({