Merge #546: a synced default Codex model and reasoning effort for new local Codex sessions

This commit is contained in:
Codeman maintainer
2026-10-09 04:53:49 +02:00
9 changed files with 246 additions and 6 deletions
+22
View File
@@ -0,0 +1,22 @@
import type { CodexConfig } from '../types.js';
import { SettingsUpdateSchema } from './schemas.js';
import { readJsonConfig, SETTINGS_PATH } from './route-helpers.js';
/** Resolve launch-only defaults without changing Codex's own configuration files. */
export async function resolveCodexLaunchDefaults(
config: CodexConfig | undefined,
customEndpoint = false
): Promise<CodexConfig | undefined> {
if (customEndpoint) return config;
const settings = await readJsonConfig<Record<string, unknown>>(SETTINGS_PATH, 'Codex launch defaults', {});
const model = SettingsUpdateSchema.shape.codexModel.safeParse(settings.codexModel);
const effort = SettingsUpdateSchema.shape.codexReasoningEffort.safeParse(settings.codexReasoningEffort);
const defaultModel = model.success ? model.data || undefined : undefined;
const defaultEffort = effort.success ? effort.data || undefined : undefined;
if (!defaultModel && !defaultEffort) return config;
return {
...config,
model: config?.model ?? defaultModel,
reasoningEffort: config?.reasoningEffort ?? defaultEffort,
};
}
+7
View File
@@ -45,6 +45,13 @@
// Exact English-source translations. Technical names, command examples, model
// names, keyboard chords, and user-authored content intentionally stay unchanged.
const ZH_CN = Object.freeze({
'Default Codex model': 'Codex 默认模型',
'Default Codex reasoning effort': 'Codex 默认思考强度',
'Use Codex configuration': '使用 Codex 配置',
'Model ID for new local Codex sessions, including WSL. Leave empty to use Codex configuration.':
'新本地 Codex 会话(包括 WSL)使用的模型 ID。留空时使用 Codex 配置。',
'Applies to new local sessions; supported levels depend on the model and Codex version. Custom endpoints, Docker and remote sessions keep their own settings.':
'应用于新本地会话;可用强度取决于模型和 Codex 版本。自定义端点、Docker 和远程会话保留自己的设置。',
'Skip to terminal': '跳转到终端',
'Go to main page': '返回主页',
'Session tabs': '会话标签页',
+24
View File
@@ -2586,6 +2586,30 @@
<div class="set-group" id="appSettingsCodexGroup">
<div class="set-group-head"><h4>Codex</h4><span class="set-scope">synced</span></div>
<div class="set-group-body">
<div class="set-row" data-search="codex default model">
<div class="set-row-text">
<span class="set-row-label">Default Codex model</span>
<span class="set-row-desc">Model ID for new local Codex sessions, including WSL. Leave empty to use Codex configuration.</span>
</div>
<input id="appSettingsCodexModel" class="set-input" type="text" maxlength="100" aria-label="Default Codex model" placeholder="Use Codex configuration" autocomplete="off" spellcheck="false">
</div>
<div class="set-row" data-search="codex default reasoning effort thinking">
<div class="set-row-text">
<span class="set-row-label">Default Codex reasoning effort</span>
<span class="set-row-desc">Applies to new local sessions; supported levels depend on the model and Codex version. Custom endpoints, Docker and remote sessions keep their own settings.</span>
</div>
<select id="appSettingsCodexReasoningEffort" class="set-select" aria-label="Default Codex reasoning effort">
<option value="">Use Codex configuration</option>
<option value="none">none</option>
<option value="minimal">minimal</option>
<option value="low">low</option>
<option value="medium">medium</option>
<option value="high">high</option>
<option value="xhigh">xhigh</option>
<option value="max">max</option>
<option value="ultra">ultra</option>
</select>
</div>
<div class="set-row" data-search="codex bypass approvals sandbox">
<div class="set-row-text">
<span class="set-row-label">Bypass approvals and sandbox</span>
+4
View File
@@ -530,6 +530,8 @@ Object.assign(CodemanApp.prototype, {
settings.codexDangerouslyBypassApprovals ?? false;
document.getElementById('appSettingsCodexAnimations').checked =
settings.codexAnimationsEnabled ?? false;
document.getElementById('appSettingsCodexModel').value = settings.codexModel ?? '';
document.getElementById('appSettingsCodexReasoningEffort').value = settings.codexReasoningEffort ?? '';
this._applyCodexSettingsVisibility();
// Claude Permissions settings
document.getElementById('appSettingsAgentTeams').checked = settings.agentTeamsEnabled ?? false;
@@ -2584,6 +2586,8 @@ Object.assign(CodemanApp.prototype, {
claudeMode: document.getElementById('appSettingsClaudeMode').value,
allowedTools: document.getElementById('appSettingsAllowedTools').value.trim(),
// Codex CLI settings
codexModel: document.getElementById('appSettingsCodexModel').value.trim(),
codexReasoningEffort: document.getElementById('appSettingsCodexReasoningEffort').value,
codexDangerouslyBypassApprovals: document.getElementById('appSettingsCodexDangerouslyBypassApprovals').checked,
codexAnimationsEnabled: document.getElementById('appSettingsCodexAnimations').checked,
// Claude Permissions settings
+14 -6
View File
@@ -115,6 +115,7 @@ import { clampEnvOverridesForOwner } from '../../session-env-clamp.js';
import { enabledClis, getCli } from '../../config/cli-registry/registry.js';
import type { NewlineSequence } from '../../config/cli-registry/types.js';
import { resolveCliLaunchError } from '../../utils/cli-launcher.js';
import { resolveCodexLaunchDefaults } from '../codex-launch-defaults.js';
import { legacyConfigForMode } from '../../session-cli-registry-bridge.js';
import { isMultiUserMode } from '../../config/multiuser.js';
import { AUTH_COOKIE_NAME } from '../middleware/auth.js';
@@ -1117,6 +1118,8 @@ export function registerSessionRoutes(
const globalNice = await ctx.getGlobalNiceConfig();
const modelConfig = await ctx.getModelConfig();
const mode = body.mode || 'claude';
const launchCodexConfig =
mode === 'codex' && !remote ? await resolveCodexLaunchDefaults(body.codexConfig) : body.codexConfig;
// Where a model override comes from is a capability, and the three answers are
// genuinely different mechanisms:
// 'flag' — the CLI takes --model, so read the value the caller sent
@@ -1131,9 +1134,10 @@ export function registerSessionRoutes(
const modelSource = getCli(mode)?.capabilities.model;
const model =
modelSource?.source === 'flag'
? (legacyConfigForMode(mode, body as unknown as Record<string, unknown>)?.[modelSource.param ?? 'model'] as
| string
| undefined)
? (legacyConfigForMode(mode, { ...body, codexConfig: launchCodexConfig } as unknown as Record<
string,
unknown
>)?.[modelSource.param ?? 'model'] as string | undefined)
: modelSource?.source === 'claude-settings-file'
? body.model || modelConfig?.defaultModel || undefined
: undefined;
@@ -1150,7 +1154,7 @@ export function registerSessionRoutes(
deepSeekConfig: gatedDeepSeekConfig,
} = await _clampExternalCliBypassForOwner(
owner,
body.codexConfig,
launchCodexConfig,
body.geminiConfig,
body.antigravityConfig,
body.piConfig,
@@ -3851,13 +3855,17 @@ export function registerSessionRoutes(
// Apply global Nice priority config and model config from settings
const niceConfig = await ctx.getGlobalNiceConfig();
const qsModelConfig = await ctx.getModelConfig();
const qsLaunchCodexConfig =
mode === 'codex' && !remote && !docker
? await resolveCodexLaunchDefaults(codexConfig, !!customModel)
: codexConfig;
// See the create path for why this is a capability rather than a mode ladder.
const qsModelSource = getCli(mode)?.capabilities.model;
const qsModel =
qsModelSource?.source === 'flag'
? (legacyConfigForMode(mode, {
openCodeConfig,
codexConfig,
codexConfig: qsLaunchCodexConfig,
geminiConfig,
antigravityConfig,
piConfig,
@@ -3879,7 +3887,7 @@ export function registerSessionRoutes(
deepSeekConfig: qsGatedDeepSeekConfig,
} = await _clampExternalCliBypassForOwner(
owner,
codexConfig,
qsLaunchCodexConfig,
geminiConfig,
antigravityConfig,
piConfig,
+6
View File
@@ -1484,6 +1484,12 @@ export const SettingsUpdateSchema = z
claudeMode: z.string().max(50).optional(),
allowedTools: z.string().max(2000).optional(),
// Codex CLI settings
codexModel: z
.string()
.max(100)
.regex(/^[a-zA-Z0-9._\-/]*$/)
.optional(),
codexReasoningEffort: z.enum(['', ...CODEX_REASONING_EFFORTS]).optional(),
codexDangerouslyBypassApprovals: z.boolean().optional(),
codexAnimationsEnabled: z.boolean().optional(),
// Terminal history and retention