Răsfoiți Sursa

Merge remote-tracking branch 'origin/rcc/plugins' into integration/dori-cleanroom

# Conflicts:
#	rust/crates/commands/src/lib.rs
#	rust/crates/claw-cli/src/main.rs
YeonGyu-Kim 2 luni în urmă
părinte
comite
2929759ded

Fișier diff suprimat deoarece este prea mare
+ 659 - 227
rust/crates/commands/src/lib.rs


+ 6 - 0
rust/crates/plugins/src/lib.rs

@@ -2578,6 +2578,9 @@ mod tests {
             },
         );
         manager.store_registry(&registry).expect("store registry");
+        manager
+            .write_enabled_state("stale@bundled", Some(true))
+            .expect("seed bundled enabled state");
 
         let installed = manager
             .list_installed_plugins()
@@ -2635,6 +2638,9 @@ mod tests {
             },
         );
         manager.store_registry(&registry).expect("store registry");
+        manager
+            .write_enabled_state("stale-external@external", Some(true))
+            .expect("seed stale external enabled state");
 
         let installed = manager
             .list_installed_plugins()

Fișier diff suprimat deoarece este prea mare
+ 967 - 95
rust/crates/rusty-claude-cli/src/main.rs


+ 156 - 0
rust/crates/tools/src/lib.rs

@@ -8,6 +8,7 @@ use api::{
     MessageRequest, MessageResponse, OutputContentBlock, ProviderClient,
     StreamEvent as ApiStreamEvent, ToolChoice, ToolDefinition, ToolResultContentBlock,
 };
+use plugins::PluginTool;
 use reqwest::blocking::Client;
 use runtime::{
     edit_file, execute_bash, glob_search, grep_search, load_system_prompt, read_file, write_file,
@@ -55,6 +56,161 @@ pub struct ToolSpec {
     pub required_permission: PermissionMode,
 }
 
+#[derive(Debug, Clone, PartialEq)]
+pub struct GlobalToolRegistry {
+    plugin_tools: Vec<PluginTool>,
+}
+
+impl GlobalToolRegistry {
+    #[must_use]
+    pub fn builtin() -> Self {
+        Self {
+            plugin_tools: Vec::new(),
+        }
+    }
+
+    pub fn with_plugin_tools(plugin_tools: Vec<PluginTool>) -> Result<Self, String> {
+        let builtin_names = mvp_tool_specs()
+            .into_iter()
+            .map(|spec| spec.name.to_string())
+            .collect::<BTreeSet<_>>();
+        let mut seen_plugin_names = BTreeSet::new();
+
+        for tool in &plugin_tools {
+            let name = tool.definition().name.clone();
+            if builtin_names.contains(&name) {
+                return Err(format!(
+                    "plugin tool `{name}` conflicts with a built-in tool name"
+                ));
+            }
+            if !seen_plugin_names.insert(name.clone()) {
+                return Err(format!("duplicate plugin tool name `{name}`"));
+            }
+        }
+
+        Ok(Self { plugin_tools })
+    }
+
+    pub fn normalize_allowed_tools(&self, values: &[String]) -> Result<Option<BTreeSet<String>>, String> {
+        if values.is_empty() {
+            return Ok(None);
+        }
+
+        let builtin_specs = mvp_tool_specs();
+        let canonical_names = builtin_specs
+            .iter()
+            .map(|spec| spec.name.to_string())
+            .chain(self.plugin_tools.iter().map(|tool| tool.definition().name.clone()))
+            .collect::<Vec<_>>();
+        let mut name_map = canonical_names
+            .iter()
+            .map(|name| (normalize_tool_name(name), name.clone()))
+            .collect::<BTreeMap<_, _>>();
+
+        for (alias, canonical) in [
+            ("read", "read_file"),
+            ("write", "write_file"),
+            ("edit", "edit_file"),
+            ("glob", "glob_search"),
+            ("grep", "grep_search"),
+        ] {
+            name_map.insert(alias.to_string(), canonical.to_string());
+        }
+
+        let mut allowed = BTreeSet::new();
+        for value in values {
+            for token in value
+                .split(|ch: char| ch == ',' || ch.is_whitespace())
+                .filter(|token| !token.is_empty())
+            {
+                let normalized = normalize_tool_name(token);
+                let canonical = name_map.get(&normalized).ok_or_else(|| {
+                    format!(
+                        "unsupported tool in --allowedTools: {token} (expected one of: {})",
+                        canonical_names.join(", ")
+                    )
+                })?;
+                allowed.insert(canonical.clone());
+            }
+        }
+
+        Ok(Some(allowed))
+    }
+
+    #[must_use]
+    pub fn definitions(&self, allowed_tools: Option<&BTreeSet<String>>) -> Vec<ToolDefinition> {
+        let builtin = mvp_tool_specs()
+            .into_iter()
+            .filter(|spec| allowed_tools.is_none_or(|allowed| allowed.contains(spec.name)))
+            .map(|spec| ToolDefinition {
+                name: spec.name.to_string(),
+                description: Some(spec.description.to_string()),
+                input_schema: spec.input_schema,
+            });
+        let plugin = self
+            .plugin_tools
+            .iter()
+            .filter(|tool| {
+                allowed_tools.is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
+            })
+            .map(|tool| ToolDefinition {
+                name: tool.definition().name.clone(),
+                description: tool.definition().description.clone(),
+                input_schema: tool.definition().input_schema.clone(),
+            });
+        builtin.chain(plugin).collect()
+    }
+
+    #[must_use]
+    pub fn permission_specs(
+        &self,
+        allowed_tools: Option<&BTreeSet<String>>,
+    ) -> Vec<(String, PermissionMode)> {
+        let builtin = mvp_tool_specs()
+            .into_iter()
+            .filter(|spec| allowed_tools.is_none_or(|allowed| allowed.contains(spec.name)))
+            .map(|spec| (spec.name.to_string(), spec.required_permission));
+        let plugin = self
+            .plugin_tools
+            .iter()
+            .filter(|tool| {
+                allowed_tools.is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
+            })
+            .map(|tool| {
+                (
+                    tool.definition().name.clone(),
+                    permission_mode_from_plugin(tool.required_permission()),
+                )
+            });
+        builtin.chain(plugin).collect()
+    }
+
+    pub fn execute(&self, name: &str, input: &Value) -> Result<String, String> {
+        if mvp_tool_specs().iter().any(|spec| spec.name == name) {
+            return execute_tool(name, input);
+        }
+        self.plugin_tools
+            .iter()
+            .find(|tool| tool.definition().name == name)
+            .ok_or_else(|| format!("unsupported tool: {name}"))?
+            .execute(input)
+            .map_err(|error| error.to_string())
+    }
+}
+
+fn normalize_tool_name(value: &str) -> String {
+    value.trim().replace('-', "_").to_ascii_lowercase()
+}
+
+fn permission_mode_from_plugin(value: &str) -> PermissionMode {
+    match value {
+        "read-only" => PermissionMode::ReadOnly,
+        "workspace-write" => PermissionMode::WorkspaceWrite,
+        "danger-full-access" => PermissionMode::DangerFullAccess,
+        other => panic!("unsupported plugin permission: {other}"),
+    }
+}
+
 #[must_use]
 #[allow(clippy::too_many_lines)]
 pub fn mvp_tool_specs() -> Vec<ToolSpec> {

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff