Browse Source

Keep plugin-aware CLI validation aligned with the shared registry

The shared /plugins command flow already routes through the plugin registry, but
allowed-tool normalization still fell back to builtin tools when registry
construction failed. This keeps plugin-related validation errors visible at the
CLI boundary and updates tools tests to use the enum-based plugin permission
API so workspace verification remains green.

Constraint: Plugin tool permissions are now strongly typed in the plugins crate
Rejected: Restore string-based permission arguments in tests | weakens the plugin API contract
Rejected: Keep builtin fallback in normalize_allowed_tools | masks plugin registry integration failures
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not silently bypass current_tool_registry() failures unless plugin-aware allowed-tool validation is intentionally being disabled
Tested: cargo test -p commands -- --nocapture; cargo test --workspace
Not-tested: Manual REPL /plugins interaction in a live session
Yeachan-Heo 2 tháng trước cách đây
mục cha
commit
b757e96c13
2 tập tin đã thay đổi với 51 bổ sung10 xóa
  1. 48 7
      rust/crates/rusty-claude-cli/src/main.rs
  2. 3 3
      rust/crates/tools/src/lib.rs

+ 48 - 7
rust/crates/rusty-claude-cli/src/main.rs

@@ -301,9 +301,7 @@ fn resolve_model_alias(model: &str) -> &str {
 }
 
 fn normalize_allowed_tools(values: &[String]) -> Result<Option<AllowedToolSet>, String> {
-    current_tool_registry()
-        .unwrap_or_else(|_| GlobalToolRegistry::builtin())
-        .normalize_allowed_tools(values)
+    current_tool_registry()?.normalize_allowed_tools(values)
 }
 
 fn current_tool_registry() -> Result<GlobalToolRegistry, String> {
@@ -3292,17 +3290,42 @@ mod tests {
         filter_tool_specs, format_compact_report, format_cost_report, format_model_report,
         format_model_switch_report, format_permissions_report, format_permissions_switch_report,
         format_resume_report, format_status_report, format_tool_call_start, format_tool_result,
-        normalize_permission_mode, parse_args, parse_git_status_metadata, print_help_to,
-        push_output_block, render_config_report, render_memory_report, render_repl_help,
-        resolve_model_alias, response_to_events, resume_supported_slash_commands, status_context,
-        CliAction, CliOutputFormat, SlashCommand, StatusUsage, DEFAULT_MODEL,
+        normalize_permission_mode, parse_args, parse_git_status_metadata, permission_policy,
+        print_help_to, push_output_block, render_config_report, render_memory_report,
+        render_repl_help, resolve_model_alias, response_to_events, resume_supported_slash_commands,
+        status_context, CliAction, CliOutputFormat, SlashCommand, StatusUsage, DEFAULT_MODEL,
     };
     use api::{MessageResponse, OutputContentBlock, Usage};
+    use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
     use runtime::{AssistantEvent, ContentBlock, ConversationMessage, MessageRole, PermissionMode};
     use serde_json::json;
     use std::path::PathBuf;
     use tools::GlobalToolRegistry;
 
+    fn registry_with_plugin_tool() -> GlobalToolRegistry {
+        GlobalToolRegistry::with_plugin_tools(vec![PluginTool::new(
+            "plugin-demo@external",
+            "plugin-demo",
+            PluginToolDefinition {
+                name: "plugin_echo".to_string(),
+                description: Some("Echo plugin payload".to_string()),
+                input_schema: json!({
+                    "type": "object",
+                    "properties": {
+                        "message": { "type": "string" }
+                    },
+                    "required": ["message"],
+                    "additionalProperties": false
+                }),
+            },
+            "echo".to_string(),
+            Vec::new(),
+            PluginToolPermission::WorkspaceWrite,
+            None,
+        )])
+        .expect("plugin tool registry should build")
+    }
+
     #[test]
     fn defaults_to_repl_when_no_args() {
         assert_eq!(
@@ -3523,6 +3546,24 @@ mod tests {
         assert_eq!(names, vec!["read_file", "grep_search"]);
     }
 
+    #[test]
+    fn filtered_tool_specs_include_plugin_tools() {
+        let filtered = filter_tool_specs(&registry_with_plugin_tool(), None);
+        let names = filtered
+            .into_iter()
+            .map(|definition| definition.name)
+            .collect::<Vec<_>>();
+        assert!(names.contains(&"bash".to_string()));
+        assert!(names.contains(&"plugin_echo".to_string()));
+    }
+
+    #[test]
+    fn permission_policy_uses_plugin_tool_permissions() {
+        let policy = permission_policy(PermissionMode::ReadOnly, &registry_with_plugin_tool());
+        let required = policy.required_mode_for("plugin_echo");
+        assert_eq!(required, PermissionMode::WorkspaceWrite);
+    }
+
     #[test]
     fn shared_help_uses_resume_annotation_copy() {
         let help = commands::render_slash_command_help();

+ 3 - 3
rust/crates/tools/src/lib.rs

@@ -3099,7 +3099,7 @@ mod tests {
         execute_tool, final_assistant_text, mvp_tool_specs, persist_agent_terminal_state,
         AgentInput, AgentJob, GlobalToolRegistry, SubagentToolExecutor,
     };
-    use plugins::{PluginTool, PluginToolDefinition};
+    use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
     use runtime::{ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError, Session};
     use serde_json::json;
 
@@ -3181,7 +3181,7 @@ mod tests {
             },
             script.display().to_string(),
             Vec::new(),
-            "workspace-write",
+            PluginToolPermission::WorkspaceWrite,
             script.parent().map(PathBuf::from),
         )])
         .expect("registry should build");
@@ -3217,7 +3217,7 @@ mod tests {
             },
             "echo".to_string(),
             Vec::new(),
-            "read-only",
+            PluginToolPermission::ReadOnly,
             None,
         )])
         .expect_err("conflicting plugin tool should fail");