From 7c2bd227d2125a7e43e549fd55c55ac06f31e866 Mon Sep 17 00:00:00 2001 From: Blomios Date: Sun, 2 Aug 2026 18:02:00 +0200 Subject: [PATCH] fix: Codex PTY launch forwards profile model as argv override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - injecte model et model_reasoning_effort du profil via -c dans argv - remplace le modèle TUI stale en config.toml - aligne PTY/interactif sur le comportement déjà garanti par CodexExecSession - test unitaire codex_pty_launch_forwards_profile_model_as_config_override --- crates/application/src/agent/lifecycle.rs | 49 +++++++++++++++++++ crates/application/tests/agent_lifecycle.rs | 54 +++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/crates/application/src/agent/lifecycle.rs b/crates/application/src/agent/lifecycle.rs index fdb093f..dad2a93 100644 --- a/crates/application/src/agent/lifecycle.rs +++ b/crates/application/src/agent/lifecycle.rs @@ -1254,6 +1254,49 @@ fn projection_model(profile: &AgentProfile) -> Option<&str> { profile.model.as_deref() } +fn is_codex_profile(profile: &AgentProfile) -> bool { + matches!(profile.structured_adapter, Some(StructuredAdapter::Codex)) + || matches!(profile.projector, Some(ProjectorKey::Codex)) + || profile + .command + .rsplit(['/', '\\']) + .next() + .unwrap_or(&profile.command) + == "codex" +} + +fn append_codex_pty_model_overrides(profile: &AgentProfile, spec: &mut SpawnSpec) { + if !is_codex_profile(profile) { + return; + } + if let Some(model) = profile + .model + .as_deref() + .map(str::trim) + .filter(|model| !model.is_empty()) + { + spec.args.push("-c".to_owned()); + spec.args + .push(format!("model={}", toml_string_literal(model))); + } + if let Some(effort) = profile + .model_reasoning_effort + .as_deref() + .map(str::trim) + .filter(|effort| !effort.is_empty()) + { + spec.args.push("-c".to_owned()); + spec.args.push(format!( + "model_reasoning_effort={}", + toml_string_literal(effort) + )); + } +} + +fn toml_string_literal(value: &str) -> String { + serde_json::to_string(value).expect("string serialization cannot fail") +} + /// Launches an agent: resolve profile + context, prepare the invocation, apply /// the context-injection plan, open a PTY at the resolved `cwd`, spawn the CLI. /// @@ -2036,6 +2079,12 @@ impl LaunchAgent { } } + // PTY/interactif Codex : le TUI conserve un état `model = ...` dans son + // CODEX_HOME isolé. Passer le modèle du profil sur l'argv garantit que le + // lancement interactif respecte l'édition IdeA, comme le chemin structuré + // le fait déjà dans `CodexExecSession`. + append_codex_pty_model_overrides(&profile, &mut spec); + // 6. Spawn the PTY at the resolved cwd; adopt its session id everywhere. let handle = self.pty.spawn(spec.clone(), size).await?; let session_id = handle.session_id; diff --git a/crates/application/tests/agent_lifecycle.rs b/crates/application/tests/agent_lifecycle.rs index 205568a..b618d4b 100644 --- a/crates/application/tests/agent_lifecycle.rs +++ b/crates/application/tests/agent_lifecycle.rs @@ -3842,6 +3842,60 @@ async fn codex_projection_folds_args_into_spawn_spec() { ); } +#[tokio::test] +async fn codex_pty_launch_forwards_profile_model_as_config_override() { + let profile = codex_profile() + .with_projector(ProjectorKey::Codex) + .with_model("gpt-5.4") + .with_model_reasoning_effort("medium"); + let (launch, agent, fs, pty, _s) = launch_with_projection( + profile, + Some(ContextInjectionPlan::File { + target: "AGENTS.md".to_owned(), + }), + Some(full_registry()), + None, + ); + let run_dir = format!("/home/me/proj/.ideai/run/{}", agent.id); + let cfg_path = format!("{run_dir}{CODEX_CONFIG_REL}"); + fs.seed_read( + &cfg_path, + "model = \"gpt-5.6-sol\"\n[mcp_servers.idea]\ncommand = \"idea\"\n", + ); + + launch + .execute(launch_input(agent.id)) + .await + .expect("launch"); + + let args = &pty.spawns()[0].args; + assert!( + args.windows(2) + .any(|w| w == ["-c".to_owned(), "model=\"gpt-5.4\"".to_owned()]), + "PTY Codex launch must override stale TUI model from config.toml, got {args:?}" + ); + assert!( + args.windows(2).any(|w| w + == [ + "-c".to_owned(), + "model_reasoning_effort=\"medium\"".to_owned() + ]), + "PTY Codex launch must also forward reasoning effort, got {args:?}" + ); + let toml = String::from_utf8( + fs.writes_ending_with(CODEX_CONFIG_REL) + .last() + .expect("a codex config write") + .1 + .clone(), + ) + .unwrap(); + assert!( + toml.contains("model = \"gpt-5.6-sol\""), + "the unmanaged stale TUI model may remain in config.toml; argv must take precedence: {toml}" + ); +} + // ---- (5) MCP decoupling — THE key case of the lot --------------------------- /// (5) A Codex profile with **no MCP capability** still gets its sandbox projected