mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/agents): persist Enabled toggle immediately and refresh overview in tests (closes #351)
This commit is contained in:
@@ -41,6 +41,36 @@ pub(crate) struct AgentEditorView {
|
||||
}
|
||||
|
||||
impl AgentEditorView {
|
||||
fn persist_current_agent(&self) {
|
||||
let ro = self
|
||||
.params_ro
|
||||
.text()
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let wr = self
|
||||
.params_wr
|
||||
.text()
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let ro_opt = if ro.is_empty() { None } else { Some(ro) };
|
||||
let wr_opt = if wr.is_empty() { None } else { Some(wr) };
|
||||
let instr_opt = {
|
||||
let t = self.instr.text().trim().to_string();
|
||||
if t.is_empty() { None } else { Some(t) }
|
||||
};
|
||||
|
||||
self.app_event_tx.send(AppEvent::UpdateAgentConfig {
|
||||
name: self.name.clone(),
|
||||
enabled: self.enabled,
|
||||
args_read_only: ro_opt,
|
||||
args_write: wr_opt,
|
||||
instructions: instr_opt,
|
||||
command: self.command.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_key_internal(&mut self, key_event: KeyEvent) -> bool {
|
||||
if !self.installed {
|
||||
match key_event.code {
|
||||
@@ -69,10 +99,12 @@ impl AgentEditorView {
|
||||
}
|
||||
KeyEvent { code: KeyCode::Left, .. } if self.field == 0 => {
|
||||
self.enabled = true;
|
||||
self.persist_current_agent();
|
||||
true
|
||||
}
|
||||
KeyEvent { code: KeyCode::Right, .. } if self.field == 0 => {
|
||||
self.enabled = false;
|
||||
self.persist_current_agent();
|
||||
true
|
||||
}
|
||||
KeyEvent { code: KeyCode::Left, .. } if self.field == 5 => {
|
||||
@@ -85,6 +117,7 @@ impl AgentEditorView {
|
||||
}
|
||||
KeyEvent { code: KeyCode::Char(' '), .. } if self.field == 0 => {
|
||||
self.enabled = !self.enabled;
|
||||
self.persist_current_agent();
|
||||
true
|
||||
}
|
||||
ev @ KeyEvent { .. } if self.field == 1 => {
|
||||
@@ -100,32 +133,7 @@ impl AgentEditorView {
|
||||
true
|
||||
}
|
||||
KeyEvent { code: KeyCode::Enter, .. } if self.field == 4 => {
|
||||
let ro = self
|
||||
.params_ro
|
||||
.text()
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let wr = self
|
||||
.params_wr
|
||||
.text()
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let ro_opt = if ro.is_empty() { None } else { Some(ro) };
|
||||
let wr_opt = if wr.is_empty() { None } else { Some(wr) };
|
||||
let instr_opt = {
|
||||
let t = self.instr.text().trim().to_string();
|
||||
if t.is_empty() { None } else { Some(t) }
|
||||
};
|
||||
self.app_event_tx.send(AppEvent::UpdateAgentConfig {
|
||||
name: self.name.clone(),
|
||||
enabled: self.enabled,
|
||||
args_read_only: ro_opt,
|
||||
args_write: wr_opt,
|
||||
instructions: instr_opt,
|
||||
command: self.command.clone(),
|
||||
});
|
||||
self.persist_current_agent();
|
||||
self.complete = true;
|
||||
self.app_event_tx.send(AppEvent::ShowAgentsOverview);
|
||||
true
|
||||
|
||||
@@ -133,6 +133,29 @@ impl ChatWidgetHarness {
|
||||
self.chat
|
||||
.insert_background_event_with_placement(message, placement, order);
|
||||
}
|
||||
AppEvent::UpdateAgentConfig {
|
||||
name,
|
||||
enabled,
|
||||
args_read_only,
|
||||
args_write,
|
||||
instructions,
|
||||
command,
|
||||
} => {
|
||||
let runtime = &*TEST_RUNTIME;
|
||||
let _guard = runtime.enter();
|
||||
self.chat.apply_agent_update(
|
||||
&name,
|
||||
enabled,
|
||||
args_read_only,
|
||||
args_write,
|
||||
instructions,
|
||||
command,
|
||||
);
|
||||
}
|
||||
AppEvent::ShowAgentsOverview => {
|
||||
self.chat.ensure_settings_overlay_section(SettingsSection::Agents);
|
||||
self.chat.show_agents_overview_ui();
|
||||
}
|
||||
AppEvent::CommitTick => {
|
||||
self.chat.on_commit_tick();
|
||||
let newly_emitted = self.drain_events();
|
||||
|
||||
@@ -32,9 +32,11 @@ use code_tui::test_helpers::{
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
|
||||
use regex_lite::{Captures, Regex};
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::Duration;
|
||||
use once_cell::sync::Lazy;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn normalize_output(text: String) -> String {
|
||||
text
|
||||
@@ -66,6 +68,52 @@ fn normalize_trailing_whitespace(text: String) -> String {
|
||||
normalized
|
||||
}
|
||||
|
||||
static ENV_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
|
||||
struct EnvGuard {
|
||||
saved: Vec<(&'static str, Option<String>)>,
|
||||
}
|
||||
|
||||
impl EnvGuard {
|
||||
fn new(keys: &[&'static str]) -> Self {
|
||||
let mut saved = Vec::with_capacity(keys.len());
|
||||
for key in keys {
|
||||
saved.push((*key, std::env::var(key).ok()));
|
||||
unsafe {
|
||||
std::env::remove_var(key);
|
||||
}
|
||||
}
|
||||
Self { saved }
|
||||
}
|
||||
|
||||
fn set_path(&self, key: &'static str, path: &Path) {
|
||||
unsafe {
|
||||
std::env::set_var(key, path);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, key: &'static str) {
|
||||
unsafe {
|
||||
std::env::remove_var(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvGuard {
|
||||
fn drop(&mut self) {
|
||||
for (key, value) in self.saved.drain(..) {
|
||||
match value {
|
||||
Some(v) => unsafe {
|
||||
std::env::set_var(key, v);
|
||||
},
|
||||
None => unsafe {
|
||||
std::env::remove_var(key);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_key(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
|
||||
KeyEvent {
|
||||
code,
|
||||
@@ -1703,6 +1751,52 @@ fn agent_status_missing_batch_displays_error() {
|
||||
insta::assert_snapshot!("agent_status_missing_batch_displays_error", output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agents_toggle_claude_opus_persists_via_slash_command() {
|
||||
let _lock = ENV_LOCK.lock().unwrap();
|
||||
let env = EnvGuard::new(&["HOME", "CODE_HOME", "CODEX_HOME"]);
|
||||
let home_dir = TempDir::new().expect("temp home");
|
||||
let code_home = TempDir::new().expect("code home");
|
||||
env.set_path("HOME", home_dir.path());
|
||||
env.set_path("CODE_HOME", code_home.path());
|
||||
env.remove("CODEX_HOME");
|
||||
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
|
||||
harness.open_agents_settings_overlay();
|
||||
|
||||
let overlay_initial = normalize_output(render_chat_widget_to_vt100(&mut harness, 100, 28));
|
||||
assert!(overlay_initial.contains("Agents"), "Agents overlay did not open");
|
||||
|
||||
harness.show_agent_editor("claude-opus-4.1");
|
||||
|
||||
let editor_frame = normalize_output(render_chat_widget_to_vt100(&mut harness, 100, 28));
|
||||
assert!(editor_frame.to_lowercase().contains("enabled"));
|
||||
|
||||
harness.send_key(make_key(KeyCode::Char(' '), KeyModifiers::NONE));
|
||||
let editor_after_toggle = normalize_output(render_chat_widget_to_vt100(&mut harness, 100, 28));
|
||||
assert!(editor_after_toggle.to_lowercase().contains("disabled"));
|
||||
for _ in 0..4 {
|
||||
harness.send_key(make_key(KeyCode::Down, KeyModifiers::NONE));
|
||||
}
|
||||
harness.send_key(make_key(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
let overview_after_save = normalize_output(render_chat_widget_to_vt100(&mut harness, 100, 28));
|
||||
let overview_lower = overview_after_save.to_lowercase();
|
||||
assert!(overview_lower.contains("claude-opus-4.1"));
|
||||
assert!(overview_lower.contains("disabled"));
|
||||
|
||||
harness.send_key(make_key(KeyCode::Esc, KeyModifiers::NONE));
|
||||
|
||||
harness.open_agents_settings_overlay();
|
||||
harness.send_key(make_key(KeyCode::Down, KeyModifiers::NONE));
|
||||
harness.send_key(make_key(KeyCode::Down, KeyModifiers::NONE));
|
||||
let overlay_reopen = normalize_output(render_chat_widget_to_vt100(&mut harness, 100, 28));
|
||||
let reopen_lower = overlay_reopen.to_lowercase();
|
||||
assert!(reopen_lower.contains("claude-opus-4.1"));
|
||||
assert!(reopen_lower.contains("disabled"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_parallel_batches_do_not_duplicate_cells() {
|
||||
let mut harness = ChatWidgetHarness::new();
|
||||
|
||||
@@ -19,3 +19,7 @@ We’ve reinstated the documented `OPENAI_WIRE_API` override. Setting `OPENAI_WI
|
||||
## #289 — Custom prompts not discovered
|
||||
|
||||
Prompt discovery now uses the same legacy-aware resolver as other config files, so `~/.codex/prompts/*.md` is picked up when `~/.code/prompts` is absent. Fresh async tests in `code-rs/core/tests/custom_prompts_discovery.rs` lock environment variables and cover CODE_HOME override, legacy fallback, dual-directory preference, and ignoring non-Markdown files.
|
||||
|
||||
## #351 — Agents toggle state not persisting
|
||||
|
||||
Thanks for the detailed report! The Agents overlay now persists the “Enabled” checkbox immediately, so you no longer need to close and reopen the menu to see the change. We also added a VT100 snapshot regression test (`agents_toggle_claude_opus_persists_via_slash_command`) to keep the toggle flow locked. The fix is merged and will ride out in the next release.
|
||||
|
||||
Reference in New Issue
Block a user