From 874232d8191bf9ab79e8d9a6fc81f1707431dd73 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:13:20 -0700 Subject: [PATCH 1/3] test(tui): pin the plugin validation field per plugin kind The sidebar assertion has failed on main since #13299 renamed the field to the generic "plugin". This adds the agent plugin case. Co-Authored-By: Claude Opus 5.5 (1M context) --- cmux-tui/crates/cmux-tui/tests/resource_cli_v2.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmux-tui/crates/cmux-tui/tests/resource_cli_v2.rs b/cmux-tui/crates/cmux-tui/tests/resource_cli_v2.rs index 46e13976cebe..67e1f6b14218 100644 --- a/cmux-tui/crates/cmux-tui/tests/resource_cli_v2.rs +++ b/cmux-tui/crates/cmux-tui/tests/resource_cli_v2.rs @@ -826,6 +826,21 @@ fn local_plugin_jsonl_never_connects_to_the_session_socket() { assert_eq!(invalid["retryable"], false); assert_eq!(invalid["details"]["field"], "sidebar_plugin"); assert!(invalid["details"]["reason"].is_string()); + + // Agent plugins share the manager but name their own resource noun. + let invalid_agent = Command::new(bin()) + .args(["--json", "--socket"]) + .arg(dir.join("missing.sock")) + .args(["agent", "plugin", "use", "Bad"]) + .env("XDG_DATA_HOME", &data) + .env("XDG_CONFIG_HOME", &config) + .env_remove("CMUX_TUI_SOCKET") + .output() + .unwrap(); + assert_eq!(invalid_agent.status.code(), Some(1)); + let invalid_agent = parse_single_json(&invalid_agent.stderr); + assert_eq!(invalid_agent["code"], "validation.invalid"); + assert_eq!(invalid_agent["details"]["field"], "agent_plugin"); fs::remove_dir_all(dir).unwrap(); } From ba2d178e6aa340472f4c88603576f62486176e03 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:37:31 -0700 Subject: [PATCH 2/3] fix(tui): name the plugin resource in plugin validation errors #13299 generalized the plugin manager to agent plugins and changed the validation field for the selected plugin from sidebar_plugin to plugin. That broke the resource contract the spec and resource_cli_v2 pin, and left SDK callers unable to tell which plugin kind failed. The field is now the kind's resource noun: sidebar_plugin or agent_plugin. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../crates/cmux-tui/src/plugin_manager.rs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/src/plugin_manager.rs b/cmux-tui/crates/cmux-tui/src/plugin_manager.rs index 8c9c584aec74..37d36d142251 100644 --- a/cmux-tui/crates/cmux-tui/src/plugin_manager.rs +++ b/cmux-tui/crates/cmux-tui/src/plugin_manager.rs @@ -55,6 +55,15 @@ impl PluginKind { Self::Agent => "agent_plugin_", } } + + /// Resource noun named by `validation.invalid` errors about the selected + /// plugin, matching the `sidebar_plugin.*` operations in the resource spec. + fn resource_field(self) -> &'static str { + match self { + Self::Sidebar => "sidebar_plugin", + Self::Agent => "agent_plugin", + } + } } /// A userland plugin build must not hold the CLI forever. @@ -353,12 +362,14 @@ fn update_command( let mut plugin = resolve_installed_plugin(&positionals[1], kind)?; let source = git_text(&plugin.dir, ["remote", "get-url", "origin"]).ok_or_else(|| { ManagerError::validation( - Some("plugin"), + Some(kind.resource_field()), format!("plugin {} has no readable origin remote", plugin.name), ) })?; validate_git_source(&source) - .map_err(|error| ManagerError::validation(Some("plugin"), error.to_string()))?; + .map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; // Build and validate a fresh clone before touching the active install. // Updating in place would let a failed pull or build leave the selected @@ -631,17 +642,21 @@ fn resolve_installed_plugin( let by_id = forced_name.is_none() && selector.starts_with(kind.id_prefix()); if by_id { validate_plugin_id_for(selector, kind) - .map_err(|error| ManagerError::validation(Some("plugin"), error.to_string()))?; + .map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; } else { validate_plugin_name(selector) - .map_err(|error| ManagerError::validation(Some("plugin"), error.to_string()))?; + .map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; } installed_plugins(kind)? .into_iter() .find(|plugin| if by_id { plugin.id == selector } else { plugin.name == selector }) .ok_or_else(|| { ManagerError::validation( - Some("plugin"), + Some(kind.resource_field()), format!("plugin {selector:?} is not installed"), ) }) From 56185937a848bb9aadc40e67049c35e198dbc28f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:43:09 -0700 Subject: [PATCH 3/3] style(tui): apply rustfmt to the plugin validation field fix Co-Authored-By: Claude Opus 5.5 (1M context) --- .../crates/cmux-tui/src/plugin_manager.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/cmux-tui/crates/cmux-tui/src/plugin_manager.rs b/cmux-tui/crates/cmux-tui/src/plugin_manager.rs index 37d36d142251..19febe0915ee 100644 --- a/cmux-tui/crates/cmux-tui/src/plugin_manager.rs +++ b/cmux-tui/crates/cmux-tui/src/plugin_manager.rs @@ -366,10 +366,9 @@ fn update_command( format!("plugin {} has no readable origin remote", plugin.name), ) })?; - validate_git_source(&source) - .map_err(|error| { - ManagerError::validation(Some(kind.resource_field()), error.to_string()) - })?; + validate_git_source(&source).map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; // Build and validate a fresh clone before touching the active install. // Updating in place would let a failed pull or build leave the selected @@ -641,15 +640,13 @@ fn resolve_installed_plugin( let selector = forced_name.unwrap_or(selector); let by_id = forced_name.is_none() && selector.starts_with(kind.id_prefix()); if by_id { - validate_plugin_id_for(selector, kind) - .map_err(|error| { - ManagerError::validation(Some(kind.resource_field()), error.to_string()) - })?; + validate_plugin_id_for(selector, kind).map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; } else { - validate_plugin_name(selector) - .map_err(|error| { - ManagerError::validation(Some(kind.resource_field()), error.to_string()) - })?; + validate_plugin_name(selector).map_err(|error| { + ManagerError::validation(Some(kind.resource_field()), error.to_string()) + })?; } installed_plugins(kind)? .into_iter()