feat(i18n): externalize tool descriptions for translation (#3912)
Add a locale-aware tool description system that loads translations from TOML files in tool_descriptions/. This enables non-English users to see tool descriptions in their language. - Add src/i18n.rs module with ToolDescriptions loader, locale detection (ZEROCLAW_LOCALE, LANG, LC_ALL env vars), and English fallback chain - Add locale config field to Config struct for explicit locale override - Create tool_descriptions/en.toml with all 47 tool descriptions - Create tool_descriptions/zh-CN.toml with Chinese translations - Integrate with ToolsSection::build() and build_tool_instructions() to resolve descriptions from locale files before hardcoded fallback - Add PromptContext.tool_descriptions field for prompt-time resolution - Add AgentBuilder.tool_descriptions() setter for Agent construction - Include tool_descriptions/ in Cargo.toml package include list - Add 8 unit tests covering locale loading, fallback chains, env detection, and config override Closes #3901
This commit is contained in:
parent
3430f9bf1a
commit
81d99f513c
@ -31,6 +31,7 @@ include = [
|
||||
"/LICENSE*",
|
||||
"/README.md",
|
||||
"/web/dist/**/*",
|
||||
"/tool_descriptions/**/*",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::agent::dispatcher::{
|
||||
use crate::agent::memory_loader::{DefaultMemoryLoader, MemoryLoader};
|
||||
use crate::agent::prompt::{PromptContext, SystemPromptBuilder};
|
||||
use crate::config::Config;
|
||||
use crate::i18n::ToolDescriptions;
|
||||
use crate::memory::{self, Memory, MemoryCategory};
|
||||
use crate::observability::{self, Observer, ObserverEvent};
|
||||
use crate::providers::{self, ChatMessage, ChatRequest, ConversationMessage, Provider};
|
||||
@ -40,6 +41,7 @@ pub struct Agent {
|
||||
route_model_by_hint: HashMap<String, String>,
|
||||
allowed_tools: Option<Vec<String>>,
|
||||
response_cache: Option<Arc<crate::memory::response_cache::ResponseCache>>,
|
||||
tool_descriptions: Option<ToolDescriptions>,
|
||||
}
|
||||
|
||||
pub struct AgentBuilder {
|
||||
@ -64,6 +66,7 @@ pub struct AgentBuilder {
|
||||
route_model_by_hint: Option<HashMap<String, String>>,
|
||||
allowed_tools: Option<Vec<String>>,
|
||||
response_cache: Option<Arc<crate::memory::response_cache::ResponseCache>>,
|
||||
tool_descriptions: Option<ToolDescriptions>,
|
||||
}
|
||||
|
||||
impl AgentBuilder {
|
||||
@ -90,6 +93,7 @@ impl AgentBuilder {
|
||||
route_model_by_hint: None,
|
||||
allowed_tools: None,
|
||||
response_cache: None,
|
||||
tool_descriptions: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +211,11 @@ impl AgentBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tool_descriptions(mut self, tool_descriptions: Option<ToolDescriptions>) -> Self {
|
||||
self.tool_descriptions = tool_descriptions;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Agent> {
|
||||
let mut tools = self
|
||||
.tools
|
||||
@ -257,6 +266,7 @@ impl AgentBuilder {
|
||||
route_model_by_hint: self.route_model_by_hint.unwrap_or_default(),
|
||||
allowed_tools: allowed,
|
||||
response_cache: self.response_cache,
|
||||
tool_descriptions: self.tool_descriptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -456,6 +466,7 @@ impl Agent {
|
||||
skills_prompt_mode: self.skills_prompt_mode,
|
||||
identity_config: Some(&self.identity_config),
|
||||
dispatcher_instructions: &instructions,
|
||||
tool_descriptions: self.tool_descriptions.as_ref(),
|
||||
};
|
||||
self.prompt_builder.build(&ctx)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::approval::{ApprovalManager, ApprovalRequest, ApprovalResponse};
|
||||
use crate::config::Config;
|
||||
use crate::i18n::ToolDescriptions;
|
||||
use crate::memory::{self, Memory, MemoryCategory};
|
||||
use crate::multimodal;
|
||||
use crate::observability::{self, runtime_trace, Observer, ObserverEvent};
|
||||
@ -3078,7 +3079,10 @@ pub(crate) async fn run_tool_call_loop(
|
||||
|
||||
/// Build the tool instruction block for the system prompt so the LLM knows
|
||||
/// how to invoke tools.
|
||||
pub(crate) fn build_tool_instructions(tools_registry: &[Box<dyn Tool>]) -> String {
|
||||
pub(crate) fn build_tool_instructions(
|
||||
tools_registry: &[Box<dyn Tool>],
|
||||
tool_descriptions: Option<&ToolDescriptions>,
|
||||
) -> String {
|
||||
let mut instructions = String::new();
|
||||
instructions.push_str("\n## Tool Use Protocol\n\n");
|
||||
instructions.push_str("To use a tool, wrap a JSON object in <tool_call></tool_call> tags:\n\n");
|
||||
@ -3094,11 +3098,14 @@ pub(crate) fn build_tool_instructions(tools_registry: &[Box<dyn Tool>]) -> Strin
|
||||
instructions.push_str("### Available Tools\n\n");
|
||||
|
||||
for tool in tools_registry {
|
||||
let desc = tool_descriptions
|
||||
.and_then(|td| td.get(tool.name()))
|
||||
.unwrap_or_else(|| tool.description());
|
||||
let _ = writeln!(
|
||||
instructions,
|
||||
"**{}**: {}\nParameters: `{}`\n",
|
||||
tool.name(),
|
||||
tool.description(),
|
||||
desc,
|
||||
tool.parameters_schema()
|
||||
);
|
||||
}
|
||||
@ -3324,6 +3331,16 @@ pub async fn run(
|
||||
.map(|b| b.board.clone())
|
||||
.collect();
|
||||
|
||||
// ── Load locale-aware tool descriptions ────────────────────────
|
||||
let i18n_locale = config
|
||||
.locale
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(crate::i18n::detect_locale);
|
||||
let i18n_search_dirs = crate::i18n::default_search_dirs(&config.workspace_dir);
|
||||
let i18n_descs = crate::i18n::ToolDescriptions::load(&i18n_locale, &i18n_search_dirs);
|
||||
|
||||
// ── Build system prompt from workspace MD files (OpenClaw framework) ──
|
||||
let skills = crate::skills::load_skills_with_config(&config.workspace_dir, &config);
|
||||
let mut tool_descs: Vec<(&str, &str)> = vec![
|
||||
@ -3453,7 +3470,7 @@ pub async fn run(
|
||||
|
||||
// Append structured tool-use instructions with schemas (only for non-native providers)
|
||||
if !native_tools {
|
||||
system_prompt.push_str(&build_tool_instructions(&tools_registry));
|
||||
system_prompt.push_str(&build_tool_instructions(&tools_registry, Some(&i18n_descs)));
|
||||
}
|
||||
|
||||
// Append deferred MCP tool names so the LLM knows what is available
|
||||
@ -3989,6 +4006,16 @@ pub async fn process_message(
|
||||
.map(|b| b.board.clone())
|
||||
.collect();
|
||||
|
||||
// ── Load locale-aware tool descriptions ────────────────────────
|
||||
let i18n_locale = config
|
||||
.locale
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(crate::i18n::detect_locale);
|
||||
let i18n_search_dirs = crate::i18n::default_search_dirs(&config.workspace_dir);
|
||||
let i18n_descs = crate::i18n::ToolDescriptions::load(&i18n_locale, &i18n_search_dirs);
|
||||
|
||||
let skills = crate::skills::load_skills_with_config(&config.workspace_dir, &config);
|
||||
let mut tool_descs: Vec<(&str, &str)> = vec![
|
||||
("shell", "Execute terminal commands."),
|
||||
@ -4054,7 +4081,7 @@ pub async fn process_message(
|
||||
config.skills.prompt_injection_mode,
|
||||
);
|
||||
if !native_tools {
|
||||
system_prompt.push_str(&build_tool_instructions(&tools_registry));
|
||||
system_prompt.push_str(&build_tool_instructions(&tools_registry, Some(&i18n_descs)));
|
||||
}
|
||||
if !deferred_section.is_empty() {
|
||||
system_prompt.push('\n');
|
||||
@ -5764,7 +5791,7 @@ Tail"#;
|
||||
std::path::Path::new("/tmp"),
|
||||
));
|
||||
let tools = tools::default_tools(security);
|
||||
let instructions = build_tool_instructions(&tools);
|
||||
let instructions = build_tool_instructions(&tools, None);
|
||||
|
||||
assert!(instructions.contains("## Tool Use Protocol"));
|
||||
assert!(instructions.contains("<tool_call>"));
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::config::IdentityConfig;
|
||||
use crate::i18n::ToolDescriptions;
|
||||
use crate::identity;
|
||||
use crate::skills::Skill;
|
||||
use crate::tools::Tool;
|
||||
@ -17,6 +18,9 @@ pub struct PromptContext<'a> {
|
||||
pub skills_prompt_mode: crate::config::SkillsPromptInjectionMode,
|
||||
pub identity_config: Option<&'a IdentityConfig>,
|
||||
pub dispatcher_instructions: &'a str,
|
||||
/// Locale-aware tool descriptions. When present, tool descriptions in
|
||||
/// prompts are resolved from the locale file instead of hardcoded values.
|
||||
pub tool_descriptions: Option<&'a ToolDescriptions>,
|
||||
}
|
||||
|
||||
pub trait PromptSection: Send + Sync {
|
||||
@ -124,11 +128,15 @@ impl PromptSection for ToolsSection {
|
||||
fn build(&self, ctx: &PromptContext<'_>) -> Result<String> {
|
||||
let mut out = String::from("## Tools\n\n");
|
||||
for tool in ctx.tools {
|
||||
let desc = ctx
|
||||
.tool_descriptions
|
||||
.and_then(|td: &ToolDescriptions| td.get(tool.name()))
|
||||
.unwrap_or_else(|| tool.description());
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"- **{}**: {}\n Parameters: `{}`",
|
||||
tool.name(),
|
||||
tool.description(),
|
||||
desc,
|
||||
tool.parameters_schema()
|
||||
);
|
||||
}
|
||||
@ -317,6 +325,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Full,
|
||||
identity_config: Some(&identity_config),
|
||||
dispatcher_instructions: "",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
|
||||
let section = IdentitySection;
|
||||
@ -345,6 +354,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Full,
|
||||
identity_config: None,
|
||||
dispatcher_instructions: "instr",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
let prompt = SystemPromptBuilder::with_defaults().build(&ctx).unwrap();
|
||||
assert!(prompt.contains("## Tools"));
|
||||
@ -380,6 +390,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Full,
|
||||
identity_config: None,
|
||||
dispatcher_instructions: "",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
|
||||
let output = SkillsSection.build(&ctx).unwrap();
|
||||
@ -418,6 +429,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Compact,
|
||||
identity_config: None,
|
||||
dispatcher_instructions: "",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
|
||||
let output = SkillsSection.build(&ctx).unwrap();
|
||||
@ -439,6 +451,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Full,
|
||||
identity_config: None,
|
||||
dispatcher_instructions: "instr",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
|
||||
let rendered = DateTimeSection.build(&ctx).unwrap();
|
||||
@ -477,6 +490,7 @@ mod tests {
|
||||
skills_prompt_mode: crate::config::SkillsPromptInjectionMode::Full,
|
||||
identity_config: None,
|
||||
dispatcher_instructions: "",
|
||||
tool_descriptions: None,
|
||||
};
|
||||
|
||||
let prompt = SystemPromptBuilder::with_defaults().build(&ctx).unwrap();
|
||||
|
||||
@ -3939,6 +3939,16 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
|
||||
let skills = crate::skills::load_skills_with_config(&workspace, &config);
|
||||
|
||||
// ── Load locale-aware tool descriptions ────────────────────────
|
||||
let i18n_locale = config
|
||||
.locale
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_else(crate::i18n::detect_locale);
|
||||
let i18n_search_dirs = crate::i18n::default_search_dirs(&workspace);
|
||||
let i18n_descs = crate::i18n::ToolDescriptions::load(&i18n_locale, &i18n_search_dirs);
|
||||
|
||||
// Collect tool descriptions for the prompt
|
||||
let mut tool_descs: Vec<(&str, &str)> = vec![
|
||||
(
|
||||
@ -4018,7 +4028,10 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
config.skills.prompt_injection_mode,
|
||||
);
|
||||
if !native_tools {
|
||||
system_prompt.push_str(&build_tool_instructions(tools_registry.as_ref()));
|
||||
system_prompt.push_str(&build_tool_instructions(
|
||||
tools_registry.as_ref(),
|
||||
Some(&i18n_descs),
|
||||
));
|
||||
}
|
||||
|
||||
// Append deferred MCP tool names so the LLM knows what is available
|
||||
@ -6768,7 +6781,7 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
"build_system_prompt should not emit protocol block directly"
|
||||
);
|
||||
|
||||
prompt.push_str(&build_tool_instructions(&[]));
|
||||
prompt.push_str(&build_tool_instructions(&[], None));
|
||||
|
||||
assert_eq!(
|
||||
prompt.matches("## Tool Use Protocol").count(),
|
||||
|
||||
@ -339,6 +339,17 @@ pub struct Config {
|
||||
/// Plugin system configuration (`[plugins]`).
|
||||
#[serde(default)]
|
||||
pub plugins: PluginsConfig,
|
||||
|
||||
/// Locale for tool descriptions (e.g. `"en"`, `"zh-CN"`).
|
||||
///
|
||||
/// When set, tool descriptions shown in system prompts are loaded from
|
||||
/// `tool_descriptions/<locale>.toml`. Falls back to English, then to
|
||||
/// hardcoded descriptions.
|
||||
///
|
||||
/// If omitted or empty, the locale is auto-detected from `ZEROCLAW_LOCALE`,
|
||||
/// `LANG`, or `LC_ALL` environment variables (defaulting to `"en"`).
|
||||
#[serde(default)]
|
||||
pub locale: Option<String>,
|
||||
}
|
||||
|
||||
/// Multi-client workspace isolation configuration.
|
||||
@ -5996,6 +6007,7 @@ impl Default for Config {
|
||||
knowledge: KnowledgeConfig::default(),
|
||||
linkedin: LinkedInConfig::default(),
|
||||
plugins: PluginsConfig::default(),
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8433,6 +8445,7 @@ default_temperature = 0.7
|
||||
knowledge: KnowledgeConfig::default(),
|
||||
linkedin: LinkedInConfig::default(),
|
||||
plugins: PluginsConfig::default(),
|
||||
locale: None,
|
||||
};
|
||||
|
||||
let toml_str = toml::to_string_pretty(&config).unwrap();
|
||||
@ -8766,6 +8779,7 @@ tool_dispatcher = "xml"
|
||||
knowledge: KnowledgeConfig::default(),
|
||||
linkedin: LinkedInConfig::default(),
|
||||
plugins: PluginsConfig::default(),
|
||||
locale: None,
|
||||
};
|
||||
|
||||
config.save().await.unwrap();
|
||||
|
||||
311
src/i18n.rs
Normal file
311
src/i18n.rs
Normal file
@ -0,0 +1,311 @@
|
||||
//! Internationalization support for tool descriptions.
|
||||
//!
|
||||
//! Loads tool descriptions from TOML locale files in `tool_descriptions/`.
|
||||
//! Falls back to English when a locale file or specific key is missing,
|
||||
//! and ultimately falls back to the hardcoded `tool.description()` value
|
||||
//! if no file-based description exists.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::debug;
|
||||
|
||||
/// Container for locale-specific tool descriptions loaded from TOML files.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToolDescriptions {
|
||||
/// Descriptions from the requested locale (may be empty if file missing).
|
||||
locale_descriptions: HashMap<String, String>,
|
||||
/// English fallback descriptions (always loaded when locale != "en").
|
||||
english_fallback: HashMap<String, String>,
|
||||
/// The resolved locale tag (e.g. "en", "zh-CN").
|
||||
locale: String,
|
||||
}
|
||||
|
||||
/// TOML structure: `[tools]` table mapping tool name -> description string.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct DescriptionFile {
|
||||
#[serde(default)]
|
||||
tools: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ToolDescriptions {
|
||||
/// Load descriptions for the given locale.
|
||||
///
|
||||
/// `search_dirs` lists directories to probe for `tool_descriptions/<locale>.toml`.
|
||||
/// The first directory containing a matching file wins.
|
||||
///
|
||||
/// Resolution:
|
||||
/// 1. Look up tool name in the locale file.
|
||||
/// 2. If missing (or locale file absent), look up in `en.toml`.
|
||||
/// 3. If still missing, callers fall back to `tool.description()`.
|
||||
pub fn load(locale: &str, search_dirs: &[PathBuf]) -> Self {
|
||||
let locale_descriptions = load_locale_file(locale, search_dirs);
|
||||
|
||||
let english_fallback = if locale == "en" {
|
||||
HashMap::new()
|
||||
} else {
|
||||
load_locale_file("en", search_dirs)
|
||||
};
|
||||
|
||||
debug!(
|
||||
locale = locale,
|
||||
locale_keys = locale_descriptions.len(),
|
||||
english_keys = english_fallback.len(),
|
||||
"tool descriptions loaded"
|
||||
);
|
||||
|
||||
Self {
|
||||
locale_descriptions,
|
||||
english_fallback,
|
||||
locale: locale.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the description for a tool by name.
|
||||
///
|
||||
/// Returns `Some(description)` if found in the locale file or English fallback.
|
||||
/// Returns `None` if neither file contains the key (caller should use hardcoded).
|
||||
pub fn get(&self, tool_name: &str) -> Option<&str> {
|
||||
self.locale_descriptions
|
||||
.get(tool_name)
|
||||
.or_else(|| self.english_fallback.get(tool_name))
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// The resolved locale tag.
|
||||
pub fn locale(&self) -> &str {
|
||||
&self.locale
|
||||
}
|
||||
|
||||
/// Create an empty instance that always returns `None` (hardcoded fallback).
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
locale_descriptions: HashMap::new(),
|
||||
english_fallback: HashMap::new(),
|
||||
locale: "en".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect the user's preferred locale from environment variables.
|
||||
///
|
||||
/// Checks `ZEROCLAW_LOCALE`, then `LANG`, then `LC_ALL`.
|
||||
/// Returns "en" if none are set or parseable.
|
||||
pub fn detect_locale() -> String {
|
||||
if let Ok(val) = std::env::var("ZEROCLAW_LOCALE") {
|
||||
let val = val.trim().to_string();
|
||||
if !val.is_empty() {
|
||||
return normalize_locale(&val);
|
||||
}
|
||||
}
|
||||
for var in &["LANG", "LC_ALL"] {
|
||||
if let Ok(val) = std::env::var(var) {
|
||||
let locale = normalize_locale(&val);
|
||||
if locale != "C" && locale != "POSIX" && !locale.is_empty() {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
"en".to_string()
|
||||
}
|
||||
|
||||
/// Normalize a raw locale string (e.g. "zh_CN.UTF-8") to a tag we use
|
||||
/// for file lookup (e.g. "zh-CN").
|
||||
fn normalize_locale(raw: &str) -> String {
|
||||
// Strip encoding suffix (.UTF-8, .utf8, etc.)
|
||||
let base = raw.split('.').next().unwrap_or(raw);
|
||||
// Replace underscores with hyphens for BCP-47-ish consistency
|
||||
base.replace('_', "-")
|
||||
}
|
||||
|
||||
/// Build the default set of search directories for locale files.
|
||||
///
|
||||
/// 1. The workspace directory itself (for project-local overrides).
|
||||
/// 2. The binary's parent directory (for installed distributions).
|
||||
/// 3. The compile-time `CARGO_MANIFEST_DIR` as a final fallback during dev.
|
||||
pub fn default_search_dirs(workspace_dir: &Path) -> Vec<PathBuf> {
|
||||
let mut dirs = vec![workspace_dir.to_path_buf()];
|
||||
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(parent) = exe.parent() {
|
||||
dirs.push(parent.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
// During development, also check the project root (where Cargo.toml lives).
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
if !dirs.contains(&manifest_dir) {
|
||||
dirs.push(manifest_dir);
|
||||
}
|
||||
|
||||
dirs
|
||||
}
|
||||
|
||||
/// Try to load and parse a locale TOML file from the first matching search dir.
|
||||
fn load_locale_file(locale: &str, search_dirs: &[PathBuf]) -> HashMap<String, String> {
|
||||
let filename = format!("tool_descriptions/{locale}.toml");
|
||||
|
||||
for dir in search_dirs {
|
||||
let path = dir.join(&filename);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(contents) => match toml::from_str::<DescriptionFile>(&contents) {
|
||||
Ok(parsed) => {
|
||||
debug!(path = %path.display(), keys = parsed.tools.len(), "loaded locale file");
|
||||
return parsed.tools;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(path = %path.display(), error = %e, "failed to parse locale file");
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// File not found in this directory, try next.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
locale = locale,
|
||||
"no locale file found in any search directory"
|
||||
);
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
/// Helper: create a temp dir with a `tool_descriptions/<locale>.toml` file.
|
||||
fn write_locale_file(dir: &Path, locale: &str, content: &str) {
|
||||
let td = dir.join("tool_descriptions");
|
||||
fs::create_dir_all(&td).unwrap();
|
||||
fs::write(td.join(format!("{locale}.toml")), content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_english_descriptions() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
write_locale_file(
|
||||
tmp.path(),
|
||||
"en",
|
||||
r#"[tools]
|
||||
shell = "Execute a shell command"
|
||||
file_read = "Read file contents"
|
||||
"#,
|
||||
);
|
||||
let descs = ToolDescriptions::load("en", &[tmp.path().to_path_buf()]);
|
||||
assert_eq!(descs.get("shell"), Some("Execute a shell command"));
|
||||
assert_eq!(descs.get("file_read"), Some("Read file contents"));
|
||||
assert_eq!(descs.get("nonexistent"), None);
|
||||
assert_eq!(descs.locale(), "en");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_to_english_when_locale_key_missing() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
write_locale_file(
|
||||
tmp.path(),
|
||||
"en",
|
||||
r#"[tools]
|
||||
shell = "Execute a shell command"
|
||||
file_read = "Read file contents"
|
||||
"#,
|
||||
);
|
||||
write_locale_file(
|
||||
tmp.path(),
|
||||
"zh-CN",
|
||||
r#"[tools]
|
||||
shell = "在工作区目录中执行 shell 命令"
|
||||
"#,
|
||||
);
|
||||
let descs = ToolDescriptions::load("zh-CN", &[tmp.path().to_path_buf()]);
|
||||
// Translated key returns Chinese.
|
||||
assert_eq!(descs.get("shell"), Some("在工作区目录中执行 shell 命令"));
|
||||
// Missing key falls back to English.
|
||||
assert_eq!(descs.get("file_read"), Some("Read file contents"));
|
||||
assert_eq!(descs.locale(), "zh-CN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_when_locale_file_missing() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
write_locale_file(
|
||||
tmp.path(),
|
||||
"en",
|
||||
r#"[tools]
|
||||
shell = "Execute a shell command"
|
||||
"#,
|
||||
);
|
||||
// Request a locale that has no file.
|
||||
let descs = ToolDescriptions::load("fr", &[tmp.path().to_path_buf()]);
|
||||
// Falls back to English.
|
||||
assert_eq!(descs.get("shell"), Some("Execute a shell command"));
|
||||
assert_eq!(descs.locale(), "fr");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fallback_when_no_files_exist() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let descs = ToolDescriptions::load("en", &[tmp.path().to_path_buf()]);
|
||||
assert_eq!(descs.get("shell"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_always_returns_none() {
|
||||
let descs = ToolDescriptions::empty();
|
||||
assert_eq!(descs.get("shell"), None);
|
||||
assert_eq!(descs.locale(), "en");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_locale_from_env() {
|
||||
// Save and restore env.
|
||||
let saved = std::env::var("ZEROCLAW_LOCALE").ok();
|
||||
let saved_lang = std::env::var("LANG").ok();
|
||||
|
||||
std::env::set_var("ZEROCLAW_LOCALE", "ja-JP");
|
||||
assert_eq!(detect_locale(), "ja-JP");
|
||||
|
||||
std::env::remove_var("ZEROCLAW_LOCALE");
|
||||
std::env::set_var("LANG", "zh_CN.UTF-8");
|
||||
assert_eq!(detect_locale(), "zh-CN");
|
||||
|
||||
// Restore.
|
||||
match saved {
|
||||
Some(v) => std::env::set_var("ZEROCLAW_LOCALE", v),
|
||||
None => std::env::remove_var("ZEROCLAW_LOCALE"),
|
||||
}
|
||||
match saved_lang {
|
||||
Some(v) => std::env::set_var("LANG", v),
|
||||
None => std::env::remove_var("LANG"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_locale_strips_encoding() {
|
||||
assert_eq!(normalize_locale("en_US.UTF-8"), "en-US");
|
||||
assert_eq!(normalize_locale("zh_CN.utf8"), "zh-CN");
|
||||
assert_eq!(normalize_locale("fr"), "fr");
|
||||
assert_eq!(normalize_locale("pt_BR"), "pt-BR");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_locale_overrides_env() {
|
||||
// This tests the precedence logic: if config provides a locale,
|
||||
// it should be used instead of detect_locale().
|
||||
// The actual override happens at the call site in prompt.rs / loop_.rs,
|
||||
// so here we just verify ToolDescriptions works with an explicit locale.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
write_locale_file(
|
||||
tmp.path(),
|
||||
"de",
|
||||
r#"[tools]
|
||||
shell = "Einen Shell-Befehl im Arbeitsverzeichnis ausführen"
|
||||
"#,
|
||||
);
|
||||
let descs = ToolDescriptions::load("de", &[tmp.path().to_path_buf()]);
|
||||
assert_eq!(
|
||||
descs.get("shell"),
|
||||
Some("Einen Shell-Befehl im Arbeitsverzeichnis ausführen")
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -54,6 +54,7 @@ pub(crate) mod hardware;
|
||||
pub(crate) mod health;
|
||||
pub(crate) mod heartbeat;
|
||||
pub mod hooks;
|
||||
pub mod i18n;
|
||||
pub(crate) mod identity;
|
||||
pub(crate) mod integrations;
|
||||
pub mod memory;
|
||||
|
||||
@ -89,6 +89,7 @@ mod hardware;
|
||||
mod health;
|
||||
mod heartbeat;
|
||||
mod hooks;
|
||||
mod i18n;
|
||||
mod identity;
|
||||
mod integrations;
|
||||
mod memory;
|
||||
|
||||
@ -193,6 +193,7 @@ pub async fn run_wizard(force: bool) -> Result<Config> {
|
||||
knowledge: crate::config::KnowledgeConfig::default(),
|
||||
linkedin: crate::config::LinkedInConfig::default(),
|
||||
plugins: crate::config::PluginsConfig::default(),
|
||||
locale: None,
|
||||
};
|
||||
|
||||
println!(
|
||||
@ -567,6 +568,7 @@ async fn run_quick_setup_with_home(
|
||||
knowledge: crate::config::KnowledgeConfig::default(),
|
||||
linkedin: crate::config::LinkedInConfig::default(),
|
||||
plugins: crate::config::PluginsConfig::default(),
|
||||
locale: None,
|
||||
};
|
||||
|
||||
config.save().await?;
|
||||
|
||||
59
tool_descriptions/en.toml
Normal file
59
tool_descriptions/en.toml
Normal file
@ -0,0 +1,59 @@
|
||||
# English tool descriptions (default locale)
|
||||
#
|
||||
# Each key under [tools] matches the tool's name() return value.
|
||||
# Values are the human-readable descriptions shown in system prompts.
|
||||
|
||||
[tools]
|
||||
backup = "Create, list, verify, and restore workspace backups"
|
||||
browser = "Web/browser automation with pluggable backends (agent-browser, rust-native, computer_use). Supports DOM actions plus optional OS-level actions (mouse_move, mouse_click, mouse_drag, key_type, key_press, screen_capture) through a computer-use sidecar. Use 'snapshot' to map interactive elements to refs (@e1, @e2). Enforces browser.allowed_domains for open actions."
|
||||
browser_delegate = "Delegate browser-based tasks to a browser-capable CLI for interacting with web applications like Teams, Outlook, Jira, Confluence"
|
||||
browser_open = "Open an approved HTTPS URL in the system browser. Security constraints: allowlist-only domains, no local/private hosts, no scraping."
|
||||
cloud_ops = "Cloud transformation advisory tool. Analyzes IaC plans, assesses migration paths, reviews costs, and checks architecture against Well-Architected Framework pillars. Read-only: does not create or modify cloud resources."
|
||||
cloud_patterns = "Cloud pattern library. Given a workload description, suggests applicable cloud-native architectural patterns (containerization, serverless, database modernization, etc.)."
|
||||
composio = "Execute actions on 1000+ apps via Composio (Gmail, Notion, GitHub, Slack, etc.). Use action='list' to see available actions (includes parameter names). action='execute' with action_name/tool_slug and params to run an action. If you are unsure of the exact params, pass 'text' instead with a natural-language description of what you want (Composio will resolve the correct parameters via NLP). action='list_accounts' or action='connected_accounts' to list OAuth-connected accounts. action='connect' with app/auth_config_id to get OAuth URL. connected_account_id is auto-resolved when omitted."
|
||||
content_search = "Search file contents by regex pattern within the workspace. Supports ripgrep (rg) with grep fallback. Output modes: 'content' (matching lines with context), 'files_with_matches' (file paths only), 'count' (match counts per file). Example: pattern='fn main', include='*.rs', output_mode='content'."
|
||||
cron_add = """Create a scheduled cron job (shell or agent) with cron/at/every schedules. Use job_type='agent' with a prompt to run the AI agent on schedule. To deliver output to a channel (Discord, Telegram, Slack, Mattermost, Matrix), set delivery={"mode":"announce","channel":"discord","to":"<channel_id_or_chat_id>"}. This is the preferred tool for sending scheduled/delayed messages to users via channels."""
|
||||
cron_list = "List all scheduled cron jobs"
|
||||
cron_remove = "Remove a cron job by id"
|
||||
cron_run = "Force-run a cron job immediately and record run history"
|
||||
cron_runs = "List recent run history for a cron job"
|
||||
cron_update = "Patch an existing cron job (schedule, command, prompt, enabled, delivery, model, etc.)"
|
||||
data_management = "Workspace data retention, purge, and storage statistics"
|
||||
delegate = "Delegate a subtask to a specialized agent. Use when: a task benefits from a different model (e.g. fast summarization, deep reasoning, code generation). The sub-agent runs a single prompt by default; with agentic=true it can iterate with a filtered tool-call loop."
|
||||
file_edit = "Edit a file by replacing an exact string match with new content"
|
||||
file_read = "Read file contents with line numbers. Supports partial reading via offset and limit. Extracts text from PDF; other binary files are read with lossy UTF-8 conversion."
|
||||
file_write = "Write contents to a file in the workspace"
|
||||
git_operations = "Perform structured Git operations (status, diff, log, branch, commit, add, checkout, stash). Provides parsed JSON output and integrates with security policy for autonomy controls."
|
||||
glob_search = "Search for files matching a glob pattern within the workspace. Returns a sorted list of matching file paths relative to the workspace root. Examples: '**/*.rs' (all Rust files), 'src/**/mod.rs' (all mod.rs in src)."
|
||||
google_workspace = "Interact with Google Workspace services (Drive, Gmail, Calendar, Sheets, Docs, etc.) via the gws CLI. Requires gws to be installed and authenticated."
|
||||
hardware_board_info = "Return full board info (chip, architecture, memory map) for connected hardware. Use when: user asks for 'board info', 'what board do I have', 'connected hardware', 'chip info', 'what hardware', or 'memory map'."
|
||||
hardware_memory_map = "Return the memory map (flash and RAM address ranges) for connected hardware. Use when: user asks for 'upper and lower memory addresses', 'memory map', 'address space', or 'readable addresses'. Returns flash/RAM ranges from datasheets."
|
||||
hardware_memory_read = "Read actual memory/register values from Nucleo via USB. Use when: user asks to 'read register values', 'read memory at address', 'dump memory', 'lower memory 0-126', or 'give address and value'. Returns hex dump. Requires Nucleo connected via USB and probe feature. Params: address (hex, e.g. 0x20000000 for RAM start), length (bytes, default 128)."
|
||||
http_request = "Make HTTP requests to external APIs. Supports GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS methods. Security constraints: allowlist-only domains, no local/private hosts, configurable timeout and response size limits."
|
||||
image_info = "Read image file metadata (format, dimensions, size) and optionally return base64-encoded data."
|
||||
knowledge = "Manage a knowledge graph of architecture decisions, solution patterns, lessons learned, and experts. Actions: capture, search, relate, suggest, expert_find, lessons_extract, graph_stats."
|
||||
linkedin = "Manage LinkedIn: create posts, list your posts, comment, react, delete posts, view engagement, get profile info, and read the configured content strategy. Requires LINKEDIN_* credentials in .env file."
|
||||
memory_forget = "Remove a memory by key. Use to delete outdated facts or sensitive data. Returns whether the memory was found and removed."
|
||||
memory_recall = "Search long-term memory for relevant facts, preferences, or context. Returns scored results ranked by relevance."
|
||||
memory_store = "Store a fact, preference, or note in long-term memory. Use category 'core' for permanent facts, 'daily' for session notes, 'conversation' for chat context, or a custom category name."
|
||||
microsoft365 = "Microsoft 365 integration: manage Outlook mail, Teams messages, Calendar events, OneDrive files, and SharePoint search via Microsoft Graph API"
|
||||
model_routing_config = "Manage default model settings, scenario-based provider/model routes, classification rules, and delegate sub-agent profiles"
|
||||
notion = "Interact with Notion: query databases, read/create/update pages, and search the workspace."
|
||||
pdf_read = "Extract plain text from a PDF file in the workspace. Returns all readable text. Image-only or encrypted PDFs return an empty result. Requires the 'rag-pdf' build feature."
|
||||
project_intel = "Project delivery intelligence: generate status reports, detect risks, draft client updates, summarize sprints, and estimate effort. Read-only analysis tool."
|
||||
proxy_config = "Manage ZeroClaw proxy settings (scope: environment | zeroclaw | services), including runtime and process env application"
|
||||
pushover = "Send a Pushover notification to your device. Requires PUSHOVER_TOKEN and PUSHOVER_USER_KEY in .env file."
|
||||
schedule = """Manage scheduled shell-only tasks. Actions: create/add/once/list/get/cancel/remove/pause/resume. WARNING: This tool creates shell jobs whose output is only logged, NOT delivered to any channel. To send a scheduled message to Discord/Telegram/Slack/Matrix, use the cron_add tool with job_type='agent' and a delivery config like {"mode":"announce","channel":"discord","to":"<channel_id>"}."""
|
||||
screenshot = "Capture a screenshot of the current screen. Returns the file path and base64-encoded PNG data."
|
||||
security_ops = "Security operations tool for managed cybersecurity services. Actions: triage_alert (classify/prioritize alerts), run_playbook (execute incident response steps), parse_vulnerability (parse scan results), generate_report (create security posture reports), list_playbooks (list available playbooks), alert_stats (summarize alert metrics)."
|
||||
shell = "Execute a shell command in the workspace directory"
|
||||
sop_advance = "Report the result of the current SOP step and advance to the next step. Provide the run_id, whether the step succeeded or failed, and a brief output summary."
|
||||
sop_approve = "Approve a pending SOP step that is waiting for operator approval. Returns the step instruction to execute. Use sop_status to see which runs are waiting."
|
||||
sop_execute = "Manually trigger a Standard Operating Procedure (SOP) by name. Returns the run ID and first step instruction. Use sop_list to see available SOPs."
|
||||
sop_list = "List all loaded Standard Operating Procedures (SOPs) with their triggers, priority, step count, and active run count. Optionally filter by name or priority."
|
||||
sop_status = "Query SOP execution status. Provide run_id for a specific run, or sop_name to list runs for that SOP. With no arguments, shows all active runs."
|
||||
swarm = "Orchestrate a swarm of agents to collaboratively handle a task. Supports sequential (pipeline), parallel (fan-out/fan-in), and router (LLM-selected) strategies."
|
||||
tool_search = """Fetch full schema definitions for deferred MCP tools so they can be called. Use "select:name1,name2" for exact match or keywords to search."""
|
||||
web_fetch = "Fetch a web page and return its content as clean plain text. HTML pages are automatically converted to readable text. JSON and plain text responses are returned as-is. Only GET requests; follows redirects. Security: allowlist-only domains, no local/private hosts."
|
||||
web_search_tool = "Search the web for information. Returns relevant search results with titles, URLs, and descriptions. Use this to find current information, news, or research topics."
|
||||
workspace = "Manage multi-client workspaces. Subcommands: list, switch, create, info, export. Each workspace provides isolated memory, audit, secrets, and tool restrictions."
|
||||
60
tool_descriptions/zh-CN.toml
Normal file
60
tool_descriptions/zh-CN.toml
Normal file
@ -0,0 +1,60 @@
|
||||
# 中文工具描述 (简体中文)
|
||||
#
|
||||
# [tools] 下的每个键对应工具的 name() 返回值。
|
||||
# 值是显示在系统提示中的人类可读描述。
|
||||
# 缺少的键将回退到英文 (en.toml) 描述。
|
||||
|
||||
[tools]
|
||||
backup = "创建、列出、验证和恢复工作区备份"
|
||||
browser = "基于可插拔后端(agent-browser、rust-native、computer_use)的网页/浏览器自动化。支持 DOM 操作以及通过 computer-use 辅助工具进行的可选系统级操作(mouse_move、mouse_click、mouse_drag、key_type、key_press、screen_capture)。使用 'snapshot' 将交互元素映射到引用(@e1、@e2)。对 open 操作强制执行 browser.allowed_domains。"
|
||||
browser_delegate = "将基于浏览器的任务委派给具有浏览器功能的 CLI,用于与 Teams、Outlook、Jira、Confluence 等 Web 应用交互"
|
||||
browser_open = "在系统浏览器中打开经批准的 HTTPS URL。安全约束:仅允许列表域名,禁止本地/私有主机,禁止抓取。"
|
||||
cloud_ops = "云转型咨询工具。分析 IaC 计划、评估迁移路径、审查成本,并根据良好架构框架支柱检查架构。只读:不创建或修改云资源。"
|
||||
cloud_patterns = "云模式库。根据工作负载描述,建议适用的云原生架构模式(容器化、无服务器、数据库现代化等)。"
|
||||
composio = "通过 Composio 在 1000 多个应用上执行操作(Gmail、Notion、GitHub、Slack 等)。使用 action='list' 查看可用操作(包含参数名称)。使用 action='execute' 配合 action_name/tool_slug 和 params 运行操作。如果不确定具体参数,可传入 'text' 并用自然语言描述需求(Composio 将通过 NLP 解析正确参数)。使用 action='list_accounts' 或 action='connected_accounts' 列出 OAuth 已连接账户。使用 action='connect' 配合 app/auth_config_id 获取 OAuth URL。省略时自动解析 connected_account_id。"
|
||||
content_search = "在工作区内按正则表达式搜索文件内容。支持 ripgrep (rg),可回退到 grep。输出模式:'content'(带上下文的匹配行)、'files_with_matches'(仅文件路径)、'count'(每个文件的匹配计数)。"
|
||||
cron_add = "创建带有 cron/at/every 计划的定时任务(shell 或 agent)。使用 job_type='agent' 配合 prompt 按计划运行 AI 代理。要将输出发送到频道(Discord、Telegram、Slack、Mattermost、Matrix),请设置 delivery 配置。这是通过频道向用户发送定时/延迟消息的首选工具。"
|
||||
cron_list = "列出所有已计划的 cron 任务"
|
||||
cron_remove = "按 ID 删除 cron 任务"
|
||||
cron_run = "立即强制运行 cron 任务并记录运行历史"
|
||||
cron_runs = "列出 cron 任务的最近运行历史"
|
||||
cron_update = "修改现有 cron 任务(计划、命令、提示、启用状态、投递配置、模型等)"
|
||||
data_management = "工作区数据保留、清理和存储统计"
|
||||
delegate = "将子任务委派给专用代理。适用场景:任务受益于不同模型(如快速摘要、深度推理、代码生成)。子代理默认运行单个提示;设置 agentic=true 后可通过过滤的工具调用循环进行迭代。"
|
||||
file_edit = "通过替换精确匹配的字符串来编辑文件"
|
||||
file_read = "读取带行号的文件内容。支持通过 offset 和 limit 进行部分读取。可从 PDF 提取文本;其他二进制文件使用有损 UTF-8 转换读取。"
|
||||
file_write = "将内容写入工作区中的文件"
|
||||
git_operations = "执行结构化的 Git 操作(status、diff、log、branch、commit、add、checkout、stash)。提供解析后的 JSON 输出,并与安全策略集成以实现自主控制。"
|
||||
glob_search = "在工作区内搜索匹配 glob 模式的文件。返回相对于工作区根目录的排序文件路径列表。示例:'**/*.rs'(所有 Rust 文件)、'src/**/mod.rs'(src 中所有 mod.rs)。"
|
||||
google_workspace = "与 Google Workspace 服务(Drive、Gmail、Calendar、Sheets、Docs 等)交互。通过 gws CLI 操作,需要 gws 已安装并认证。"
|
||||
hardware_board_info = "返回已连接硬件的完整板卡信息(芯片、架构、内存映射)。适用场景:用户询问板卡信息、连接的硬件、芯片信息等。"
|
||||
hardware_memory_map = "返回已连接硬件的内存映射(Flash 和 RAM 地址范围)。适用场景:用户询问内存地址、地址空间或可读地址。返回数据手册中的 Flash/RAM 范围。"
|
||||
hardware_memory_read = "通过 USB 从 Nucleo 读取实际内存/寄存器值。适用场景:用户要求读取寄存器值、读取内存地址、转储内存等。返回十六进制转储。需要 Nucleo 通过 USB 连接并启用 probe 功能。"
|
||||
http_request = "向外部 API 发送 HTTP 请求。支持 GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS 方法。安全约束:仅允许列表域名,禁止本地/私有主机,可配置超时和响应大小限制。"
|
||||
image_info = "读取图片文件元数据(格式、尺寸、大小),可选返回 base64 编码数据。"
|
||||
knowledge = "管理架构决策、解决方案模式、经验教训和专家的知识图谱。操作:capture、search、relate、suggest、expert_find、lessons_extract、graph_stats。"
|
||||
linkedin = "管理 LinkedIn:创建帖子、列出帖子、评论、点赞、删除帖子、查看互动数据、获取个人资料信息,以及阅读配置的内容策略。需要在 .env 文件中配置 LINKEDIN_* 凭据。"
|
||||
memory_forget = "按键删除记忆。用于删除过时事实或敏感数据。返回记忆是否被找到并删除。"
|
||||
memory_recall = "在长期记忆中搜索相关事实、偏好或上下文。返回按相关性排名的评分结果。"
|
||||
memory_store = "在长期记忆中存储事实、偏好或笔记。使用类别 'core' 存储永久事实,'daily' 存储会话笔记,'conversation' 存储聊天上下文,或使用自定义类别名称。"
|
||||
microsoft365 = "Microsoft 365 集成:通过 Microsoft Graph API 管理 Outlook 邮件、Teams 消息、日历事件、OneDrive 文件和 SharePoint 搜索"
|
||||
model_routing_config = "管理默认模型设置、基于场景的提供商/模型路由、分类规则和委派子代理配置"
|
||||
notion = "与 Notion 交互:查询数据库、读取/创建/更新页面、搜索工作区。"
|
||||
pdf_read = "从工作区中的 PDF 文件提取纯文本。返回所有可读文本。仅图片或加密的 PDF 返回空结果。需要 'rag-pdf' 构建功能。"
|
||||
project_intel = "项目交付智能:生成状态报告、检测风险、起草客户更新、总结冲刺、估算工作量。只读分析工具。"
|
||||
proxy_config = "管理 ZeroClaw 代理设置(范围:environment | zeroclaw | services),包括运行时和进程环境应用"
|
||||
pushover = "向设备发送 Pushover 通知。需要在 .env 文件中配置 PUSHOVER_TOKEN 和 PUSHOVER_USER_KEY。"
|
||||
schedule = "管理仅限 shell 的定时任务。操作:create/add/once/list/get/cancel/remove/pause/resume。警告:此工具创建的 shell 任务输出仅记录日志,不会发送到任何频道。要向 Discord/Telegram/Slack/Matrix 发送定时消息,请使用 cron_add 工具。"
|
||||
screenshot = "捕获当前屏幕截图。返回文件路径和 base64 编码的 PNG 数据。"
|
||||
security_ops = "托管网络安全服务的安全运营工具。操作:triage_alert(分类/优先级排序警报)、run_playbook(执行事件响应步骤)、parse_vulnerability(解析扫描结果)、generate_report(创建安全态势报告)、list_playbooks(列出可用剧本)、alert_stats(汇总警报指标)。"
|
||||
shell = "在工作区目录中执行 shell 命令"
|
||||
sop_advance = "报告当前 SOP 步骤的结果并前进到下一步。提供 run_id、步骤是否成功或失败,以及简短的输出摘要。"
|
||||
sop_approve = "批准等待操作员批准的待处理 SOP 步骤。返回要执行的步骤指令。使用 sop_status 查看哪些运行正在等待。"
|
||||
sop_execute = "按名称手动触发标准操作程序 (SOP)。返回运行 ID 和第一步指令。使用 sop_list 查看可用 SOP。"
|
||||
sop_list = "列出所有已加载的标准操作程序 (SOP),包括触发器、优先级、步骤数和活跃运行数。可按名称或优先级筛选。"
|
||||
sop_status = "查询 SOP 执行状态。提供 run_id 查看特定运行,或提供 sop_name 列出该 SOP 的所有运行。无参数时显示所有活跃运行。"
|
||||
swarm = "编排代理群以协作处理任务。支持顺序(管道)、并行(扇出/扇入)和路由器(LLM 选择)策略。"
|
||||
tool_search = "获取延迟 MCP 工具的完整 schema 定义以便调用。使用 \"select:name1,name2\" 精确匹配或关键词搜索。"
|
||||
web_fetch = "获取网页并以纯文本形式返回内容。HTML 页面自动转换为可读文本。JSON 和纯文本响应按原样返回。仅 GET 请求;跟随重定向。安全:仅允许列表域名,禁止本地/私有主机。"
|
||||
web_search_tool = "搜索网络获取信息。返回包含标题、URL 和描述的相关搜索结果。用于查找当前信息、新闻或研究主题。"
|
||||
workspace = "管理多客户端工作区。子命令:list、switch、create、info、export。每个工作区提供隔离的记忆、审计、密钥和工具限制。"
|
||||
Loading…
Reference in New Issue
Block a user