Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/codex-model-nudge-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/adapters/cli/coco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,16 @@ export function createCocoAdapter(pathOverride?: string): CliAdapter {
sandboxReadonlyPaths: () => [...TRAE_MIGRATION_DONE_MARKERS],
get resolvedBin(): string { return (cachedBin ??= resolveCommand(rawBin)); },

buildArgs({ sessionId, resume, model, disableCliBypass }) {
buildArgs({ sessionId, resume, model, disableCliBypass, hideRateLimitModelNudge }) {
const args: string[] = [];
if (resume) {
args.push('--resume', sessionId);
} else {
args.push('--session-id', sessionId);
}
if (!disableCliBypass) args.push('--yolo');
// CoCo on traecli shares the low-quota picker; use its long config flag.
if (hideRateLimitModelNudge) args.push('--config', 'notice.hide_rate_limit_model_nudge=true');
if (model && model.trim()) {
// CoCo expects nested key path for model override. `model=...` exits 1,
// while `model.name=...` starts correctly.
Expand Down
13 changes: 9 additions & 4 deletions src/adapters/cli/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,17 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter {
authPaths: ['~/.codex'],
get resolvedBin(): string { return (cachedBin ??= resolveCommand(rawBin)); },

buildArgs({ sessionId, resume, resumeSessionId, forkSession, workingDir, model, reasoningEffort, disableCliBypass, bypassHookTrust, readIsolation, remoteWsUrl, remoteThreadId, shellSubprocessEnv }) {
buildArgs({ sessionId, resume, resumeSessionId, forkSession, workingDir, model, reasoningEffort, disableCliBypass, bypassHookTrust, hideRateLimitModelNudge, readIsolation, remoteWsUrl, remoteThreadId, shellSubprocessEnv }) {
// Hybrid RPC input mode: attach this TUI to the botmux-owned app-server
// thread. User input is delivered out-of-band via JSON-RPC (turn/start,
// see codex-rpc-engine + worker), so the pane is a pure viewer — no paste
// path, no history.jsonl verify. --no-alt-screen keeps pane capture working.
// A submit Enter can accept Codex's low-quota picker (default: switch).
// Suppress it at the TUI boundary, including the RPC viewer. Keep this
// independent of approval/sandbox bypass and leave user config untouched.
const modelNudgeArgs = hideRateLimitModelNudge
? ['-c', 'notice.hide_rate_limit_model_nudge=true']
: [];
if (remoteWsUrl && remoteThreadId) {
// -c check_for_update_on_startup=false: an RPC pane is a pure viewer with
// NO terminal input path, so codex's interactive "Update available … Press
Expand All @@ -196,7 +202,7 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter {
// it suppressed like the startup update picker.
return ['--remote', remoteWsUrl, 'resume', '--no-alt-screen',
'-c', 'check_for_update_on_startup=false',
'-c', 'notice.hide_rate_limit_model_nudge=true',
...modelNudgeArgs,
remoteThreadId];
}
// Read isolation for Codex is enforced by the worker's Seatbelt wrapper,
Expand Down Expand Up @@ -240,8 +246,7 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter {
// (never show again)"; never written to the user's global config. Added
// on BOTH TUI launch shapes (this plain pane and the --remote viewer
// above); app-server/runner CLIs render no TUI popup and need no flag.
'-c',
'notice.hide_rate_limit_model_nudge=true',
...modelNudgeArgs,
];
// Under read isolation the worker denies bots.json, so `botmux send` (a shell
// subprocess) registers this bot from the worker-written cred FILE, keyed by
Expand Down
10 changes: 8 additions & 2 deletions src/adapters/cli/traex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,12 @@ export function createTraexAdapter(pathOverride?: string): CliAdapter {
sandboxReadonlyPaths: () => [...TRAE_MIGRATION_DONE_MARKERS],
get resolvedBin(): string { return (cachedBin ??= resolveCommand(rawBin)); },

buildArgs({ sessionId, resume, resumeSessionId, workingDir, model, reasoningEffort, modelBackendVariant, disableCliBypass, bypassHookTrust, remoteWsUrl, remoteThreadId, shellSubprocessEnv, nativeSubagentRuntimeHookCommand }) {
buildArgs({ sessionId, resume, resumeSessionId, workingDir, model, reasoningEffort, modelBackendVariant, disableCliBypass, bypassHookTrust, hideRateLimitModelNudge, remoteWsUrl, remoteThreadId, shellSubprocessEnv, nativeSubagentRuntimeHookCommand }) {
// TraeX shares Codex's low-quota picker and notice setting. Disable it
// per process so a message-submit Enter cannot confirm a model switch.
const modelNudgeArgs = hideRateLimitModelNudge
? ['-c', 'notice.hide_rate_limit_model_nudge=true']
: [];
// Hybrid RPC input mode (codex-family): attach the TUI to the botmux-owned
// app-server thread; input flows via JSON-RPC (see codex-rpc-engine + worker)
// instead of a drop-prone paste. TRAE CLI shares codex's --remote/resume
Expand All @@ -236,7 +241,7 @@ export function createTraexAdapter(pathOverride?: string): CliAdapter {
// -c check_for_update_on_startup=false: RPC pane has no terminal input path,
// so an interactive update dialog would freeze the resume. TraeX shares
// codex's config schema; disable at the process level, never user-global.
return ['--remote', remoteWsUrl, 'resume', '--no-alt-screen', '-c', 'check_for_update_on_startup=false', remoteThreadId];
return ['--remote', remoteWsUrl, 'resume', '--no-alt-screen', '-c', 'check_for_update_on_startup=false', ...modelNudgeArgs, remoteThreadId];
}
const baseArgs = [
...(!disableCliBypass ? [
Expand All @@ -252,6 +257,7 @@ export function createTraexAdapter(pathOverride?: string): CliAdapter {
...(bypassHookTrust ? ['--dangerously-bypass-hook-trust'] : []),
] : []),
'--no-alt-screen',
...modelNudgeArgs,
...goalEnvConfigArgs(),
];
// Keep trigger-user identity wrappers available in tool shells. Set only
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ export interface CliAdapter {
* treated as false by adapters (the worker always sends an explicit boolean
* for codex/traex). Does NOT apply to `--remote`/app-server/exec paths. */
bypassHookTrust?: boolean;
/** Codex-family (codex/traex/coco): suppress the low-quota model-switch picker per process.
* The worker supplies the global default-ON setting; false/absent adds no override. */
hideRateLimitModelNudge?: boolean;
/** Optional session-scoped skill plugin/root prepared by botmux. */
skillPluginDir?: string;
/** True when this session runs under per-bot read isolation (the worker
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ export const config = {
// stored `false` disables it. The daemon ANDs this with each bot's
// `!disableCliBypass` before handing it to the adapter (see worker init).
get bypassCodexHookTrust(): boolean { return readGlobalConfig().dashboard?.bypassCodexHookTrust !== false; },
get hideCodexRateLimitModelNudge(): boolean { return readGlobalConfig().dashboard?.hideCodexRateLimitModelNudge !== false; },
};

// allowedUsers is mutable — daemon resolves email prefixes to open_ids at startup
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ interface ResolvedDashboardSettings {
/** Whether botmux auto-bypasses Codex's interactive hook-trust gate for
* Codex-family plain-TUI launches. Default ON (only an explicit false disables). */
bypassCodexHookTrust: boolean;
hideCodexRateLimitModelNudge: boolean;
codexNotifier: {
enabled: boolean;
targetBotAppId: string | null;
Expand Down Expand Up @@ -1615,6 +1616,7 @@ function resolveDashboardSettings(): ResolvedDashboardSettings {
codexRpcInput: dashboard.codexRpcInput === true, // default OFF until live-verified
// default ON — only an explicit stored false disables (matches config.ts getter)
bypassCodexHookTrust: dashboard.bypassCodexHookTrust !== false,
hideCodexRateLimitModelNudge: dashboard.hideCodexRateLimitModelNudge !== false,
codexNotifier: {
enabled: codexNotifier.enabled,
targetBotAppId: codexNotifier.targetBotAppId ?? null,
Expand Down
8 changes: 8 additions & 0 deletions src/dashboard/settings-write-applier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface ResolvedDashboardSettingsView {
herdrTraexPlugin: { enabled: boolean; source: string; ref: string; recommendedSource: string; recommendedRef: string };
codexRpcInput: boolean;
bypassCodexHookTrust: boolean;
hideCodexRateLimitModelNudge: boolean;
codexNotifier: {
enabled: boolean;
targetBotAppId: string | null;
Expand Down Expand Up @@ -214,6 +215,7 @@ export type ApplySettingsWriteError =
| 'invalid_herdrTraexPlugin_ref'
| 'invalid_codexRpcInput'
| 'invalid_bypassCodexHookTrust'
| 'invalid_hideCodexRateLimitModelNudge'
| 'invalid_codexNotifier'
| 'invalid_codexNotifier_enabled'
| 'invalid_codexNotifier_targetBotAppId'
Expand Down Expand Up @@ -437,6 +439,12 @@ export async function applySettingsWrite(
}
patch.bypassCodexHookTrust = obj.bypassCodexHookTrust;
}
if ('hideCodexRateLimitModelNudge' in obj) {
if (typeof obj.hideCodexRateLimitModelNudge !== 'boolean') {
return { ok: false, error: 'invalid_hideCodexRateLimitModelNudge' };
}
patch.hideCodexRateLimitModelNudge = obj.hideCodexRateLimitModelNudge;
}
if ('noVisibleOutputHint' in obj) {
if (typeof obj.noVisibleOutputHint !== 'boolean') {
return { ok: false, error: 'invalid_noVisibleOutputHint' };
Expand Down
4 changes: 4 additions & 0 deletions src/dashboard/web/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,8 @@ const zh = {
'settings.herdrTraexUnsupported': '当前 herdr 不支持插件;需 ≥ 0.7.0,请先运行 herdr update',
'settings.codexRpcInput': 'Codex 家族 RPC 输入模式',
'settings.codexRpcInputHelp': '实验性,默认关闭。对 codex / traex 机器人(仅 tmux 后端),用户消息经 app-server JSON-RPC 通道注入,绕开 codex 终端粘贴丢消息的问题(pane 仍跑真实 --remote TUI 渲染)。sandbox/隔离/审批门控/wrapper/带启动命令的会话自动回退传统粘贴。',
'settings.hideCodexRateLimitModelNudge': '关闭 Codex 系额度换模型提醒',
'settings.hideCodexRateLimitModelNudgeHelp': '默认开启。关闭 Codex / TraeX / CoCo 在额度接近上限时的换模型弹窗,避免发送消息的回车误确认切换。关闭后遵循 CLI 自身配置;走 Aiden 网关启动的会话不受此保护。新启动的 CLI 进程生效,不会切回已有会话的模型,也不会增加额度。',
'settings.bypassCodexHookTrust': '自动信任 Codex Hook',
'settings.bypassCodexHookTrustHelp': '默认开启。Codex 0.14x 会弹交互式 hook 信任门(Press t to trust);botmux 每次升级重写自装 hook 会让 hash 变化、门重新弹出,托管会话没人能按 t → 首条消息卡死。开启后对 codex / traex 普通 TUI 传 --dangerously-bypass-hook-trust。注意:该 flag 信任 codex 见到的所有 hook 来源(含项目 .codex/hooks.json 与已启用插件),不只 botmux 自装的;不想自动信任第三方项目/插件 hook 可关闭。受限机器人(关绕过)始终不受影响。下次会话生效,不影响已存活的 pane。',
'settings.codexNotifier': 'Codex 任务完成通知',
Expand Down Expand Up @@ -4458,6 +4460,8 @@ const en: Record<keyof typeof zh, string> = {
'settings.herdrTraexUnsupported': 'This herdr does not support plugins; requires >= 0.7.0. Run herdr update first',
'settings.codexRpcInput': 'Codex-family RPC input mode',
'settings.codexRpcInputHelp': 'Experimental, off by default. For codex / traex bots (tmux backend only), user messages are injected via the app-server JSON-RPC channel, bypassing codex terminal paste-drops (the pane still runs the real --remote TUI). Sandbox/isolation/approval-gated/wrapper/startup-command sessions fall back to paste.',
'settings.hideCodexRateLimitModelNudge': 'Hide Codex-family quota model-switch reminder',
'settings.hideCodexRateLimitModelNudgeHelp': 'On by default. Suppresses the Codex / TraeX / CoCo low-quota model-switch picker so a message submit cannot accidentally confirm a switch. Turn off to follow CLI settings. Sessions launched through the Aiden gateway are not protected by this setting. Takes effect for newly started CLI processes. Does not restore existing session models or increase quota.',
'settings.bypassCodexHookTrust': 'Auto-trust Codex hooks',
'settings.bypassCodexHookTrustHelp': 'On by default. Codex 0.14x shows an interactive hook-trust gate ("Press t to trust"); every botmux upgrade rewrites its bundled hook so the hash changes and the gate re-fires, and a botmux-managed session has no one to press t → the first message wedges. When on, codex / traex plain-TUI launches pass --dangerously-bypass-hook-trust. Note: that flag trusts ALL hook sources codex sees (including a project .codex/hooks.json and enabled plugins), not only botmux\'s — turn it off if you do not want third-party project/plugin hooks auto-trusted. Restricted bots (bypass disabled) are never affected. Takes effect on the next session; running panes are unchanged.',
'settings.codexNotifier': 'Codex task completion notifications',
Expand Down
11 changes: 10 additions & 1 deletion src/dashboard/web/settings-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface DashboardSettings {
};
codexRpcInput: boolean;
bypassCodexHookTrust: boolean;
hideCodexRateLimitModelNudge: boolean;
codexNotifier: {
enabled: boolean;
targetBotAppId: string | null;
Expand Down Expand Up @@ -179,6 +180,7 @@ function parseSettings(s: any): DashboardSettings {
codexRpcInput: s?.codexRpcInput === true,
// default ON — only an explicit persisted false disables (matches server snapshot)
bypassCodexHookTrust: s?.bypassCodexHookTrust !== false,
hideCodexRateLimitModelNudge: s?.hideCodexRateLimitModelNudge !== false,
codexNotifier: {
enabled: s?.codexNotifier?.enabled === true,
targetBotAppId: typeof s?.codexNotifier?.targetBotAppId === 'string'
Expand Down Expand Up @@ -720,7 +722,7 @@ function SettingsBody(props: {
const autoUpdateDisabled = !canWrite || settings.localDevInstall || !settings.autoUpdateSupported;
const autoRestartDisabled = !canWrite || settings.maintenance.autoUpdate?.enabled !== true;

const saveBoolean = (key: 'publicReadOnly' | 'openTerminalInFeishu' | 'enableLocalCliOpen' | 'chatBotDiscovery' | 'codexRpcInput' | 'bypassCodexHookTrust' | 'noVisibleOutputHint' | 'remoteAccess', value: boolean) => {
const saveBoolean = (key: 'publicReadOnly' | 'openTerminalInFeishu' | 'enableLocalCliOpen' | 'chatBotDiscovery' | 'codexRpcInput' | 'bypassCodexHookTrust' | 'hideCodexRateLimitModelNudge' | 'noVisibleOutputHint' | 'remoteAccess', value: boolean) => {
void props.onSave(key, { [key]: value }, s => ({ ...s, [key]: value }));
};
const saveHerdrTraexPlugin = (patch: Partial<Pick<DashboardSettings['herdrTraexPlugin'], 'enabled' | 'source' | 'ref'>>) => {
Expand Down Expand Up @@ -870,6 +872,13 @@ function SettingsBody(props: {
disabled={dis || savingKey === 'bypassCodexHookTrust'}
onChange={value => saveBoolean('bypassCodexHookTrust', value)}
/>
<ToggleRow
title={tr('settings.hideCodexRateLimitModelNudge')}
help={tr('settings.hideCodexRateLimitModelNudgeHelp')}
checked={settings.hideCodexRateLimitModelNudge}
disabled={dis || savingKey === 'hideCodexRateLimitModelNudge'}
onChange={value => saveBoolean('hideCodexRateLimitModelNudge', value)}
/>
<CodexNotifierSettingsEditor
value={settings.codexNotifier}
disabled={dis}
Expand Down
5 changes: 5 additions & 0 deletions src/global-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ export interface DashboardGlobalConfig {
* fail-closed lower bound (a restricted bot never gets it regardless). Read live
* by the daemon — see config.ts `bypassCodexHookTrust`. */
bypassCodexHookTrust?: boolean;
/** Suppress Codex/TraeX/CoCo's low-quota model-switch picker for managed launches.
* Default ON; false leaves the CLI's own notice configuration in control.
* Applied per process; never edits the user's CLI config. Aiden's gateway cannot forward it. */
hideCodexRateLimitModelNudge?: boolean;
/** Experimental: inject the "no visible output" anti-resend guidance into the
* botmux routing hints. Counters Claude Code (≥2.1.212) thinking-only nudges
* that make a model resend after a silent `botmux send`-only turn. Default OFF
Expand Down Expand Up @@ -450,6 +454,7 @@ function readDashboard(raw: unknown): DashboardGlobalConfig | undefined {
// getter (config.ts `bypassCodexHookTrust`) treats absent as ON, so we must
// preserve a stored `false` to let an operator disable it.
if (typeof d.bypassCodexHookTrust === 'boolean') out.bypassCodexHookTrust = d.bypassCodexHookTrust;
if (typeof d.hideCodexRateLimitModelNudge === 'boolean') out.hideCodexRateLimitModelNudge = d.hideCodexRateLimitModelNudge;
if (typeof d.noVisibleOutputHint === 'boolean') out.noVisibleOutputHint = d.noVisibleOutputHint;
// 非法值(非数字 / NaN / 越界)静默丢弃,走 card-builder 的默认 80。
if (typeof d.contextCompactThreshold === 'number'
Expand Down
2 changes: 1 addition & 1 deletion src/setup/cli-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export function stripSettingsArgs(args: ReadonlyArray<string>): string[] {
* 路径底层 claude 从不注入此 flag,剥除是 no-op。
* 这些参数承载的 session 环境已在进程级 env(BOTMUX_SESSION_ID 等)注入、并被 wrapper
* 子进程继承(见 worker.ts childEnv),故剥掉只是去掉一条冗余的 belt-and-suspenders
* 通道,不丢功能。关闭启动更新的覆盖在 aiden 路径无法传递(launcher 本身禁止 config);
* 通道。关闭启动更新和额度换模型提醒的覆盖在 aiden 路径无法传递(launcher 本身禁止 config);
* worker 会在极少数仍出现选择器的启动中自动选择非升级项,host 侧仍会做每日只读检查。
* `-c` 按 botmux 注入白名单精确识别,用户自带的
* `-c key=val` 一律不动;`--settings` 则沿用 aiden x claude 历来的兼容策略
Expand Down
1 change: 1 addition & 0 deletions src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14489,6 +14489,7 @@ async function spawnCli(
// plain-TUI launch doesn't wedge on codex 0.14x's "Press t to trust" gate.
// The adapter further ANDs this with !disableCliBypass. Read live per spawn.
bypassHookTrust: config.bypassCodexHookTrust,
hideRateLimitModelNudge: config.hideCodexRateLimitModelNudge,
skillPluginDir: cfg.skillPluginDir,
// Per-bot CODEX_HOME can be enabled without the OS sandbox. Only the latter
// needs Codex's read-isolation shell-env behavior.
Expand Down
Loading