fix: reconcile non-cli approval governance with current dev APIs

This commit is contained in:
Chummy 2026-02-26 00:03:55 +08:00 committed by Chum Yin
parent 1fcf2df28b
commit d8a1d1d14c
6 changed files with 206 additions and 38 deletions

View File

@ -982,6 +982,14 @@ pub(crate) async fn run_tool_call_loop(
anyhow::bail!("Agent exceeded maximum tool iterations ({max_iterations})")
}
/// Build the tool instruction block for the system prompt from concrete tool
/// specs so the LLM knows how to invoke tools.
pub(crate) fn build_tool_instructions(tools_registry: &[Box<dyn Tool>]) -> String {
let specs: Vec<crate::tools::ToolSpec> =
tools_registry.iter().map(|tool| tool.spec()).collect();
build_tool_instructions_from_specs(&specs)
}
/// Build the tool instruction block for the system prompt from concrete tool
/// specs so the LLM knows how to invoke tools.
pub(crate) fn build_tool_instructions_from_specs(tool_specs: &[crate::tools::ToolSpec]) -> String {

View File

@ -68,10 +68,11 @@ pub use whatsapp::WhatsAppChannel;
pub use whatsapp_web::WhatsAppWebChannel;
use crate::agent::loop_::{
build_shell_policy_instructions, build_tool_instructions, run_tool_call_loop, scrub_credentials,
build_shell_policy_instructions, build_tool_instructions_from_specs, run_tool_call_loop,
scrub_credentials,
};
use crate::approval::ApprovalManager;
use crate::config::Config;
use crate::approval::{ApprovalManager, PendingApprovalError};
use crate::config::{Config, NonCliNaturalLanguageApprovalMode};
use crate::identity;
use crate::memory::{self, Memory};
use crate::observability::{self, runtime_trace, Observer};
@ -81,7 +82,6 @@ use crate::security::SecurityPolicy;
use crate::tools::{self, Tool};
use crate::util::truncate_with_ellipsis;
use anyhow::{Context, Result};
use regex::Regex;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
@ -244,7 +244,7 @@ struct ChannelRuntimeContext {
interrupt_on_new_message: bool,
multimodal: crate::config::MultimodalConfig,
hooks: Option<Arc<crate::hooks::HookRunner>>,
non_cli_excluded_tools: Arc<Vec<String>>,
non_cli_excluded_tools: Arc<Mutex<Vec<String>>>,
query_classification: crate::config::QueryClassificationConfig,
model_routes: Vec<crate::config::ModelRouteConfig>,
approval_manager: Arc<ApprovalManager>,
@ -691,13 +691,21 @@ fn parse_runtime_command(channel_name: &str, content: &str) -> Option<ChannelRun
.next()
.unwrap_or(command_token)
.to_ascii_lowercase();
let args: Vec<&str> = parts.collect();
let tail = args.join(" ").trim().to_string();
match base_command.as_str() {
// History reset commands are safe for all channels.
"/new" | "/clear" => Some(ChannelRuntimeCommand::NewSession),
"/approve-request" => Some(ChannelRuntimeCommand::RequestToolApproval(tail)),
"/approve-confirm" => Some(ChannelRuntimeCommand::ConfirmToolApproval(tail)),
"/approve-pending" => Some(ChannelRuntimeCommand::ListPendingApprovals),
"/approve" => Some(ChannelRuntimeCommand::ApproveTool(tail)),
"/unapprove" => Some(ChannelRuntimeCommand::UnapproveTool(tail)),
"/approvals" => Some(ChannelRuntimeCommand::ListApprovals),
// Provider/model switching remains limited to channels with session routing.
"/models" if supports_runtime_model_switch(channel_name) => {
if let Some(provider) = parts.next() {
if let Some(provider) = args.first() {
Some(ChannelRuntimeCommand::SetProvider(
provider.trim().to_string(),
))
@ -706,7 +714,7 @@ fn parse_runtime_command(channel_name: &str, content: &str) -> Option<ChannelRun
}
}
"/model" if supports_runtime_model_switch(channel_name) => {
let model = parts.collect::<Vec<_>>().join(" ").trim().to_string();
let model = tail;
if model.is_empty() {
Some(ChannelRuntimeCommand::ShowModel)
} else {
@ -717,6 +725,76 @@ fn parse_runtime_command(channel_name: &str, content: &str) -> Option<ChannelRun
}
}
fn is_runtime_token(value: &str) -> bool {
let token = value.trim();
!token.is_empty()
&& token
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.' | ':'))
}
fn extract_runtime_tail_token(text: &str, prefixes: &[&str]) -> Option<String> {
prefixes.iter().find_map(|prefix| {
text.strip_prefix(prefix).and_then(|rest| {
let token = rest.trim();
if is_runtime_token(token) {
Some(token.to_string())
} else {
None
}
})
})
}
fn parse_natural_language_runtime_command(content: &str) -> Option<ChannelRuntimeCommand> {
let trimmed = content.trim();
if trimmed.is_empty() {
return None;
}
let lower = trimmed.to_ascii_lowercase();
if matches!(
lower.as_str(),
"show pending approvals" | "list pending approvals" | "pending approvals"
) {
return Some(ChannelRuntimeCommand::ListPendingApprovals);
}
if trimmed == "查看授权"
|| matches!(
lower.as_str(),
"show approvals" | "list approvals" | "approvals"
)
{
return Some(ChannelRuntimeCommand::ListApprovals);
}
if let Some(request_id) = extract_runtime_tail_token(&lower, &["confirm "]) {
return Some(ChannelRuntimeCommand::ConfirmToolApproval(request_id));
}
if let Some(request_id) = extract_runtime_tail_token(trimmed, &["确认授权 "]) {
return Some(ChannelRuntimeCommand::ConfirmToolApproval(request_id));
}
if let Some(tool) =
extract_runtime_tail_token(&lower, &["revoke tool ", "unapprove ", "revoke "])
{
return Some(ChannelRuntimeCommand::UnapproveTool(tool));
}
if let Some(tool) = extract_runtime_tail_token(trimmed, &["撤销工具 ", "取消授权 "]) {
return Some(ChannelRuntimeCommand::UnapproveTool(tool));
}
if let Some(tool) = extract_runtime_tail_token(&lower, &["approve tool ", "approve "]) {
return Some(ChannelRuntimeCommand::RequestToolApproval(tool));
}
if let Some(tool) = extract_runtime_tail_token(trimmed, &["授权工具 ", "请放开 ", "放开 "])
{
return Some(ChannelRuntimeCommand::RequestToolApproval(tool));
}
None
}
fn is_approval_management_command(command: &ChannelRuntimeCommand) -> bool {
matches!(
command,
@ -4464,7 +4542,9 @@ pub async fn start_channels(config: Config) -> Result<()> {
} else {
None
},
non_cli_excluded_tools: Arc::new(config.autonomy.non_cli_excluded_tools.clone()),
non_cli_excluded_tools: Arc::new(Mutex::new(
config.autonomy.non_cli_excluded_tools.clone(),
)),
query_classification: config.query_classification.clone(),
model_routes: config.model_routes.clone(),
approval_manager: Arc::new(ApprovalManager::from_config(&config.autonomy)),
@ -4768,7 +4848,7 @@ mod tests {
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -4822,7 +4902,7 @@ mod tests {
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -4879,7 +4959,7 @@ mod tests {
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -5478,6 +5558,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(vec!["mock_price".to_string()])),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
&crate::config::AutonomyConfig::default(),
)),
@ -5548,14 +5630,14 @@ BTC is currently around $65,000 based on latest tool output."#
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
interrupt_on_new_message: false,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
&crate::config::AutonomyConfig::default(),
)),
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
});
process_channel_message(
@ -5612,14 +5694,14 @@ BTC is currently around $65,000 based on latest tool output."#
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
interrupt_on_new_message: false,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
&crate::config::AutonomyConfig::default(),
)),
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
});
process_channel_message(
@ -5838,7 +5920,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -5902,7 +5984,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -5975,7 +6057,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -6079,6 +6161,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
assert_eq!(
@ -6212,6 +6296,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
assert_eq!(
@ -6320,6 +6406,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager,
});
@ -6422,6 +6510,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(vec!["shell".to_string()])),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager,
});
@ -6514,6 +6604,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
@ -6647,6 +6739,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
@ -6760,6 +6854,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
@ -6853,6 +6949,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
@ -6965,6 +7063,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(&autonomy_cfg)),
});
@ -7077,7 +7177,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7153,7 +7253,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7244,7 +7344,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7385,6 +7485,8 @@ BTC is currently around $65,000 based on latest tool output."#
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
&crate::config::AutonomyConfig::default(),
)),
@ -7479,7 +7581,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7544,7 +7646,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7720,7 +7822,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7805,7 +7907,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: true,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7902,7 +8004,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: true,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -7981,7 +8083,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -8045,7 +8147,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -8566,7 +8668,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -8656,7 +8758,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -8746,7 +8848,7 @@ BTC is currently around $65,000 based on latest tool output."#
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -9377,7 +9479,7 @@ BTC is currently around $65,000 based on latest tool output."#;
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(
@ -9448,7 +9550,7 @@ BTC is currently around $65,000 based on latest tool output."#;
interrupt_on_new_message: false,
multimodal: crate::config::MultimodalConfig::default(),
hooks: None,
non_cli_excluded_tools: Arc::new(Vec::new()),
non_cli_excluded_tools: Arc::new(Mutex::new(Vec::new())),
query_classification: crate::config::QueryClassificationConfig::default(),
model_routes: Vec::new(),
approval_manager: Arc::new(ApprovalManager::from_config(

View File

@ -7,7 +7,7 @@ use directories::UserDirs;
use parking_lot::Mutex;
use reqwest::multipart::{Form, Part};
use std::fmt::Write as _;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tokio::fs;

View File

@ -11,7 +11,7 @@ pub use schema::{
DockerRuntimeConfig, EmbeddingRouteConfig, EstopConfig, FeishuConfig, GatewayConfig,
GroupReplyConfig, GroupReplyMode, HardwareConfig, HardwareTransport, HeartbeatConfig,
HooksConfig, HttpRequestConfig, IMessageConfig, IdentityConfig, LarkConfig, MatrixConfig,
MemoryConfig, ModelRouteConfig, MultimodalConfig, NextcloudTalkConfig, ObservabilityConfig,
MultimodalConfig, NextcloudTalkConfig, NonCliNaturalLanguageApprovalMode, ObservabilityConfig,
OtpConfig, OtpMethod, PeripheralBoardConfig, PeripheralsConfig, ProviderConfig, ProxyConfig,
ProxyScope, QdrantConfig, QueryClassificationConfig, ReliabilityConfig, ResearchPhaseConfig,
ResearchTrigger, ResourceLimitsConfig, RuntimeConfig, SandboxBackend, SandboxConfig,

View File

@ -2450,6 +2450,9 @@ impl Default for AutonomyConfig {
always_ask: default_always_ask(),
allowed_roots: Vec::new(),
non_cli_excluded_tools: default_non_cli_excluded_tools(),
non_cli_approval_approvers: Vec::new(),
non_cli_natural_language_approval_mode: NonCliNaturalLanguageApprovalMode::default(),
non_cli_natural_language_approval_mode_by_channel: HashMap::new(),
}
}
}

View File

@ -720,6 +720,61 @@ impl BrowserTool {
Ok(())
}
async fn resolve_output_path_for_write(
&self,
key: &str,
path: &str,
) -> anyhow::Result<PathBuf> {
let trimmed = path.trim();
self.validate_output_path(key, trimmed)?;
tokio::fs::create_dir_all(&self.security.workspace_dir).await?;
let workspace_root = tokio::fs::canonicalize(&self.security.workspace_dir)
.await
.unwrap_or_else(|_| self.security.workspace_dir.clone());
let raw_path = Path::new(trimmed);
let output_path = if raw_path.is_absolute() {
raw_path.to_path_buf()
} else {
workspace_root.join(raw_path)
};
let parent = output_path
.parent()
.ok_or_else(|| anyhow::anyhow!("'{key}' path has no parent directory"))?;
tokio::fs::create_dir_all(parent).await?;
let resolved_parent = tokio::fs::canonicalize(parent).await?;
if !self.security.is_resolved_path_allowed(&resolved_parent) {
anyhow::bail!(
"{}",
self.security
.resolved_path_violation_message(&resolved_parent)
);
}
match tokio::fs::symlink_metadata(&output_path).await {
Ok(meta) => {
if meta.file_type().is_symlink() {
anyhow::bail!(
"Refusing to write browser output through symlink: {}",
output_path.display()
);
}
if !meta.is_file() {
anyhow::bail!(
"Browser output path is not a regular file: {}",
output_path.display()
);
}
}
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => return Err(err.into()),
}
Ok(output_path)
}
fn validate_computer_use_action(
&self,
action: &str,
@ -1127,7 +1182,7 @@ impl Tool for BrowserTool {
});
}
let mut action = match parse_browser_action(action_str, &args) {
let action = match parse_browser_action(action_str, &args) {
Ok(a) => a,
Err(e) => {
return Ok(ToolResult {