Skip to content
Open
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/adapters/cli/codex-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function createCodexAppAdapter(pathOverride?: string): CliAdapter {
return [(cachedCodexBin ??= resolveCommandReal(rawCodexBin))];
},

buildArgs({ sessionId, resume, resumeSessionId, workingDir, botName, botOpenId, locale, model, reasoningEffort, codexBrowser }) {
buildArgs({ sessionId, resume, resumeSessionId, quietResume, workingDir, botName, botOpenId, locale, model, reasoningEffort, codexBrowser }) {
const args = [
runnerArgv0('codex-app-runner', runnerPath()),
'--session-id', sessionId,
Expand All @@ -71,6 +71,7 @@ export function createCodexAppAdapter(pathOverride?: string): CliAdapter {
'--codex-bin', (cachedCodexBin ??= resolveCommandReal(rawCodexBin)),
];
if (resume && resumeSessionId) args.push('--thread-id', resumeSessionId);
if (quietResume) args.push('--strict-resume');
pushOpt(args, '--cwd', workingDir);
pushOpt(args, '--bot-name', botName);
pushOpt(args, '--bot-open-id', botOpenId);
Expand Down
8 changes: 5 additions & 3 deletions src/adapters/cli/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ 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 }) {
buildArgs({ sessionId, resume, resumeSessionId, quietResume, forkSession, workingDir, model, reasoningEffort, disableCliBypass, bypassHookTrust, readIsolation, remoteWsUrl, remoteThreadId }) {
// 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
Expand All @@ -188,7 +188,8 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter {
// enter to continue" dialog would block the resume forever and freeze the
// Web terminal. Disable the check at the PROCESS level (never the user's
// global config). The bounded startup-dialog watcher is only a fail-safe.
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',
...(quietResume ? ['-c', 'tui.auto_recap=false'] : []), remoteThreadId];
}
// Read isolation for Codex is enforced by the worker's Seatbelt wrapper,
// NOT by codex's own profile (codex 0.137 can't express a read blocklist).
Expand Down Expand Up @@ -264,7 +265,8 @@ export function createCodexAdapter(pathOverride?: string): CliAdapter {
// privilege-escalation guard on fork. Falls back to plain `resume` when we
// somehow lack a source id (nothing to fork from).
const codexArgs = codexSessionId
? [forkSession ? 'fork' : 'resume', ...baseArgs, codexSessionId]
? [forkSession ? 'fork' : 'resume', ...baseArgs,
...(quietResume && !forkSession ? ['-c', 'tui.auto_recap=false'] : []), codexSessionId]
: freshArgs;
return codexArgs;
},
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 @@ -131,6 +131,9 @@ export interface CliAdapter {
workingDir?: string;
/** CLI-native session id used for resume when it differs from botmux's session id. */
resumeSessionId?: string;
/** Maintenance resume with no new input: suppress automatic recap/inference
* and require the original thread where the adapter supports strict resume. */
quietResume?: boolean;
/** When true, resume the `resumeSessionId` transcript but write forward into a
* NEW CLI-native session id instead of the resumed one, leaving the source
* transcript untouched — the native "fork/branch a session" primitive
Expand Down
11 changes: 9 additions & 2 deletions src/codex-app-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface Args {
controlSocketPath?: string;
controlLocatorPath?: string;
threadId?: string;
strictResume?: boolean;
botName?: string;
botOpenId?: string;
locale?: string;
Expand Down Expand Up @@ -258,6 +259,7 @@ function parseArgs(argv: string[]): Args {
else if (key === '--codex-bin' && val !== undefined) { out.codexBin = val; i++; }
else if (key === '--cwd' && val !== undefined) { out.cwd = val; i++; }
else if (key === '--thread-id' && val !== undefined) { out.threadId = val; i++; }
else if (key === '--strict-resume') out.strictResume = true;
else if (key === '--bot-name' && val !== undefined) { out.botName = val; i++; }
else if (key === '--bot-open-id' && val !== undefined) { out.botOpenId = val; i++; }
else if (key === '--locale' && val !== undefined) { out.locale = val; i++; }
Expand All @@ -267,6 +269,7 @@ function parseArgs(argv: string[]): Args {
else if (key === '--browser-plugin-root' && val !== undefined) { out.browserPluginRoot = val; i++; }
}
if (!out.sessionId) throw new Error('--session-id is required');
if (out.strictResume && !out.threadId) throw new Error('--strict-resume requires --thread-id');
if (!controlBootstrapPath) throw new Error(`${CODEX_APP_CONTROL_BOOTSTRAP_ENV} is required`);
const control = consumeCodexAppControlBootstrap(controlBootstrapPath, out.sessionId);
out.controlGeneration = control.generation;
Expand Down Expand Up @@ -1496,16 +1499,20 @@ async function ensureThread(startupDeadlineAtMs?: number): Promise<string> {
persistExtendedHistory: true,
}, { timeoutMs: startupRequestTimeout(startupDeadlineAtMs, 'thread/resume') });
const resumedThreadId = String(resumed.thread.id);
if (args.strictResume && resumedThreadId !== threadId) {
throw new Error(`Strict resume expected thread ${threadId}, received ${resumedThreadId}`);
}
threadId = resumedThreadId;
threadReady = true;
emitMarker('thread', { threadId: resumedThreadId });
return resumedThreadId;
} catch (err: any) {
// A transport error or timeout is an ambiguous acceptance boundary. It
// must never fork history by silently creating a fresh thread. Only an
// explicit app-server "missing thread" rejection permits fallback.
// explicit app-server "missing thread" rejection permits normal fallback;
// maintenance resumes must preserve the original thread in every case.
if (isActiveWriterConflict(err)) throw new CodexAppActiveWriterError(threadId, err);
if (!isExplicitMissingThread(err)) throw err;
if (args.strictResume || !isExplicitMissingThread(err)) throw err;
writeLine(`[codex-app] resume failed, starting a fresh thread: ${err?.message ?? err}`);
threadId = undefined;
threadReady = false;
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ export const config = {
// ON. A per-bot codexRpcInput:true still force-enables; the dashboard toggle
// sets this global explicitly.
get codexRpcInputDefault(): boolean { return readGlobalConfig().dashboard?.codexRpcInput === true; },
// Default ON; read live so a Dashboard change gates the next session upgrade
// without restarting daemons or changing the current turn.
get autoUpgradeCodexSessions(): boolean { return readGlobalConfig().dashboard?.autoUpgradeCodexSessions !== false; },
// Live getter (like codexRpcInputDefault): re-reads the experimental global
// toggle that gates the "no visible output" anti-resend guidance in the botmux
// routing hints, so a Settings change takes effect on the next session without
Expand Down
13 changes: 13 additions & 0 deletions src/core/worker-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12109,6 +12109,19 @@ function setupWorkerHandlers(
break;
}

case 'cli_runtime_version': {
if (!ownsLifecycleMutation()) break;
ds.cliVersion = msg.version;
dashboardEventBus.publish({
type: 'session.update',
body: {
sessionId: ds.session.sessionId,
patch: { cliVersion: msg.version },
},
});
break;
}

case 'runner_build_ready': {
const identity = runtimeBuildIdentity();
if (
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,7 @@ interface ResolvedDashboardSettings {
* source the SPA can offer as a one-click fill; never persisted unless picked. */
herdrTraexPlugin: { enabled: boolean; source: string; ref: string; recommendedSource: string; recommendedRef: string };
codexRpcInput: boolean;
autoUpgradeCodexSessions: boolean;
/** 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;
Expand Down Expand Up @@ -1594,6 +1595,7 @@ function resolveDashboardSettings(): ResolvedDashboardSettings {
recommendedRef: TRAEX_RECOMMENDED_REF,
},
codexRpcInput: dashboard.codexRpcInput === true, // default OFF until live-verified
autoUpgradeCodexSessions: dashboard.autoUpgradeCodexSessions !== false,
// default ON — only an explicit stored false disables (matches config.ts getter)
bypassCodexHookTrust: dashboard.bypassCodexHookTrust !== false,
codexNotifier: {
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 @@ -50,6 +50,7 @@ export interface ResolvedDashboardSettingsView {
chatBotDiscovery: boolean;
herdrTraexPlugin: { enabled: boolean; source: string; ref: string; recommendedSource: string; recommendedRef: string };
codexRpcInput: boolean;
autoUpgradeCodexSessions: boolean;
bypassCodexHookTrust: boolean;
codexNotifier: {
enabled: boolean;
Expand Down Expand Up @@ -213,6 +214,7 @@ export type ApplySettingsWriteError =
| 'invalid_herdrTraexPlugin_source'
| 'invalid_herdrTraexPlugin_ref'
| 'invalid_codexRpcInput'
| 'invalid_autoUpgradeCodexSessions'
| 'invalid_bypassCodexHookTrust'
| 'invalid_codexNotifier'
| 'invalid_codexNotifier_enabled'
Expand Down Expand Up @@ -431,6 +433,12 @@ export async function applySettingsWrite(
}
patch.codexRpcInput = obj.codexRpcInput;
}
if ('autoUpgradeCodexSessions' in obj) {
if (typeof obj.autoUpgradeCodexSessions !== 'boolean') {
return { ok: false, error: 'invalid_autoUpgradeCodexSessions' };
}
patch.autoUpgradeCodexSessions = obj.autoUpgradeCodexSessions;
}
if ('bypassCodexHookTrust' in obj) {
if (typeof obj.bypassCodexHookTrust !== 'boolean') {
return { ok: false, error: 'invalid_bypassCodexHookTrust' };
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 @@ -1489,6 +1489,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.autoUpgradeCodexSessions': '自动升级会话 Codex 版本',
'settings.autoUpgradeCodexSessionsHelp': '默认开启。跟随本机已安装的新版 Codex,在安全空闲时更新会话进程并恢复原会话,不额外发起推理。后台任务或无法确认安全状态的会话会延后升级。开关实时生效。',
'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 @@ -4293,6 +4295,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.autoUpgradeCodexSessions': 'Automatically upgrade session Codex versions',
'settings.autoUpgradeCodexSessionsHelp': 'On by default. Follow a newer Codex version already installed on this machine, replacing safely idle processes and resuming the same thread without additional inference. Defer sessions with background work or uncertain safety. Changes take effect live.',
'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 @@ -28,6 +28,7 @@ interface DashboardSettings {
recommendedRef: string;
};
codexRpcInput: boolean;
autoUpgradeCodexSessions: boolean;
bypassCodexHookTrust: boolean;
codexNotifier: {
enabled: boolean;
Expand Down Expand Up @@ -177,6 +178,7 @@ function parseSettings(s: any): DashboardSettings {
recommendedRef: typeof s?.herdrTraexPlugin?.recommendedRef === 'string' ? s.herdrTraexPlugin.recommendedRef : '',
},
codexRpcInput: s?.codexRpcInput === true,
autoUpgradeCodexSessions: s?.autoUpgradeCodexSessions !== false,
// default ON — only an explicit persisted false disables (matches server snapshot)
bypassCodexHookTrust: s?.bypassCodexHookTrust !== false,
codexNotifier: {
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' | 'autoUpgradeCodexSessions' | 'bypassCodexHookTrust' | '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 @@ -863,6 +865,13 @@ function SettingsBody(props: {
disabled={dis || savingKey === 'codexRpcInput'}
onChange={value => saveBoolean('codexRpcInput', value)}
/>
<ToggleRow
title={tr('settings.autoUpgradeCodexSessions')}
help={tr('settings.autoUpgradeCodexSessionsHelp')}
checked={settings.autoUpgradeCodexSessions}
disabled={dis || savingKey === 'autoUpgradeCodexSessions'}
onChange={value => saveBoolean('autoUpgradeCodexSessions', value)}
/>
<ToggleRow
title={tr('settings.bypassCodexHookTrust')}
help={tr('settings.bypassCodexHookTrustHelp')}
Expand Down
5 changes: 5 additions & 0 deletions src/global-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ export interface DashboardGlobalConfig {
* see config.ts `codexRpcInputDefault`. A per-bot `codexRpcInput: true` still
* force-enables regardless of this global default. */
codexRpcInput?: boolean;
/** Automatically replace outdated Codex session processes when safely idle
* and resume the same thread without sending a new prompt. Default ON;
* read live so an explicit false stops scheduling upgrades without restart. */
autoUpgradeCodexSessions?: boolean;
/** Whether botmux auto-bypasses Codex's interactive hook-trust gate ("Press t
* to trust") for Codex-family plain-TUI launches (codex / traex). Codex 0.14x
* gates the botmux-installed ~/.codex/hooks.json behind a manual trust prompt,
Expand Down Expand Up @@ -433,6 +437,7 @@ function readDashboard(raw: unknown): DashboardGlobalConfig | undefined {
const herdrTraexPlugin = readHerdrTraexPlugin(d.herdrTraexPlugin);
if (herdrTraexPlugin) out.herdrTraexPlugin = herdrTraexPlugin;
if (typeof d.codexRpcInput === 'boolean') out.codexRpcInput = d.codexRpcInput;
if (typeof d.autoUpgradeCodexSessions === 'boolean') out.autoUpgradeCodexSessions = d.autoUpgradeCodexSessions;
// Round-trip an explicit boolean either way. Absent stays absent — the live
// getter (config.ts `bypassCodexHookTrust`) treats absent as ON, so we must
// preserve a stored `false` to let an operator disable it.
Expand Down
Loading
Loading