Files
zed/crates/settings_content/src/agent.rs
T
Marshall Bowers a92df1eee4 Remove Burn Mode code (#46950)
This PR removes the code for Burn Mode, as we won't need it anymore
after the 17th.

Closes CLO-79.

Release Notes:

- N/A
2026-01-15 21:28:33 +00:00

743 lines
27 KiB
Rust

use collections::{HashMap, IndexMap};
use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
use std::sync::Arc;
use std::{borrow::Cow, path::PathBuf};
use crate::ExtendingVec;
use crate::{DockPosition, DockSide};
#[with_fallible_options]
#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)]
pub struct AgentSettingsContent {
/// Whether the Agent is enabled.
///
/// Default: true
pub enabled: Option<bool>,
/// Whether to show the agent panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
/// Where to dock the agent panel.
///
/// Default: right
pub dock: Option<DockPosition>,
/// Where to dock the utility pane (the thread view pane).
///
/// Default: left
pub agents_panel_dock: Option<DockSide>,
/// Default width in pixels when the agent panel is docked to the left or right.
///
/// Default: 640
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_width: Option<f32>,
/// Default height in pixels when the agent panel is docked to the bottom.
///
/// Default: 320
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_height: Option<f32>,
/// The default model to use when creating new chats and for other features when a specific model is not specified.
pub default_model: Option<LanguageModelSelection>,
/// Favorite models to show at the top of the model selector.
#[serde(default)]
pub favorite_models: Vec<LanguageModelSelection>,
/// Model to use for the inline assistant. Defaults to default_model when not specified.
pub inline_assistant_model: Option<LanguageModelSelection>,
/// Model to use for the inline assistant when streaming tools are enabled.
///
/// Default: true
pub inline_assistant_use_streaming_tools: Option<bool>,
/// Model to use for generating git commit messages.
///
/// Default: true
pub commit_message_model: Option<LanguageModelSelection>,
/// Model to use for generating thread summaries. Defaults to default_model when not specified.
pub thread_summary_model: Option<LanguageModelSelection>,
/// Additional models with which to generate alternatives when performing inline assists.
pub inline_alternatives: Option<Vec<LanguageModelSelection>>,
/// The default profile to use in the Agent.
///
/// Default: write
pub default_profile: Option<Arc<str>>,
/// Which view type to show by default in the agent panel.
///
/// Default: "thread"
pub default_view: Option<DefaultAgentView>,
/// The available agent profiles.
pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
/// Whenever a tool action would normally wait for your confirmation
/// that you allow it, always choose to allow it.
///
/// This setting has no effect on external agents that support permission modes, such as Claude Code.
///
/// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code.
///
/// Default: false
pub always_allow_tool_actions: Option<bool>,
/// Where to show a popup notification when the agent is waiting for user input.
///
/// Default: "primary_screen"
pub notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
/// Whether to play a sound when the agent has either completed its response, or needs user input.
///
/// Default: false
pub play_sound_when_agent_done: Option<bool>,
/// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
///
/// Default: true
pub single_file_review: Option<bool>,
/// Additional parameters for language model requests. When making a request
/// to a model, parameters will be taken from the last entry in this list
/// that matches the model's provider and name. In each entry, both provider
/// and model are optional, so that you can specify parameters for either
/// one.
///
/// Default: []
#[serde(default)]
pub model_parameters: Vec<LanguageModelParameters>,
/// Whether to show thumb buttons for feedback in the agent panel.
///
/// Default: true
pub enable_feedback: Option<bool>,
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
///
/// Default: true
pub expand_edit_card: Option<bool>,
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
///
/// Default: true
pub expand_terminal_card: Option<bool>,
/// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
///
/// Default: false
pub use_modifier_to_send: Option<bool>,
/// Minimum number of lines of height the agent message editor should have.
///
/// Default: 4
pub message_editor_min_lines: Option<usize>,
/// Whether to show turn statistics (elapsed time during generation, final turn duration).
///
/// Default: false
pub show_turn_stats: Option<bool>,
/// Per-tool permission rules for granular control over which tool actions require confirmation.
///
/// This setting only applies to the native Zed agent. External agent servers (Claude Code, Gemini CLI, etc.)
/// have their own permission systems and are not affected by these settings.
pub tool_permissions: Option<ToolPermissionsContent>,
}
impl AgentSettingsContent {
pub fn set_dock(&mut self, dock: DockPosition) {
self.dock = Some(dock);
}
pub fn set_model(&mut self, language_model: LanguageModelSelection) {
// let model = language_model.id().0.to_string();
// let provider = language_model.provider_id().0.to_string();
// self.default_model = Some(LanguageModelSelection {
// provider: provider.into(),
// model,
// });
self.default_model = Some(language_model)
}
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
self.inline_assistant_model = Some(LanguageModelSelection {
provider: provider.into(),
model,
});
}
pub fn set_inline_assistant_use_streaming_tools(&mut self, use_tools: bool) {
self.inline_assistant_use_streaming_tools = Some(use_tools);
}
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
self.commit_message_model = Some(LanguageModelSelection {
provider: provider.into(),
model,
});
}
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
self.thread_summary_model = Some(LanguageModelSelection {
provider: provider.into(),
model,
});
}
pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
self.always_allow_tool_actions = Some(allow);
}
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
self.play_sound_when_agent_done = Some(allow);
}
pub fn set_single_file_review(&mut self, allow: bool) {
self.single_file_review = Some(allow);
}
pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
self.use_modifier_to_send = Some(always_use);
}
pub fn set_profile(&mut self, profile_id: Arc<str>) {
self.default_profile = Some(profile_id);
}
pub fn add_favorite_model(&mut self, model: LanguageModelSelection) {
if !self.favorite_models.contains(&model) {
self.favorite_models.push(model);
}
}
pub fn remove_favorite_model(&mut self, model: &LanguageModelSelection) {
self.favorite_models.retain(|m| m != model);
}
pub fn set_tool_default_mode(&mut self, tool_id: &str, mode: ToolPermissionMode) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_id))
.or_default();
tool_rules.default_mode = Some(mode);
}
pub fn add_tool_allow_pattern(&mut self, tool_name: &str, pattern: String) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_name))
.or_default();
let always_allow = tool_rules.always_allow.get_or_insert_default();
if !always_allow.0.iter().any(|r| r.pattern == pattern) {
always_allow.0.push(ToolRegexRule {
pattern,
case_sensitive: None,
});
}
}
pub fn add_tool_deny_pattern(&mut self, tool_name: &str, pattern: String) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_name))
.or_default();
let always_deny = tool_rules.always_deny.get_or_insert_default();
if !always_deny.0.iter().any(|r| r.pattern == pattern) {
always_deny.0.push(ToolRegexRule {
pattern,
case_sensitive: None,
});
}
}
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct AgentProfileContent {
pub name: Arc<str>,
#[serde(default)]
pub tools: IndexMap<Arc<str>, bool>,
/// Whether all context servers are enabled by default.
pub enable_all_context_servers: Option<bool>,
#[serde(default)]
pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
/// The default language model selected when using this profile.
pub default_model: Option<LanguageModelSelection>,
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ContextServerPresetContent {
pub tools: IndexMap<Arc<str>, bool>,
}
#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum DefaultAgentView {
#[default]
Thread,
TextThread,
}
#[derive(
Copy,
Clone,
Default,
Debug,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum NotifyWhenAgentWaiting {
#[default]
PrimaryScreen,
AllScreens,
Never,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
pub struct LanguageModelSelection {
pub provider: LanguageModelProviderSetting,
pub model: String,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
pub struct LanguageModelParameters {
pub provider: Option<LanguageModelProviderSetting>,
pub model: Option<String>,
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub temperature: Option<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, MergeFrom)]
pub struct LanguageModelProviderSetting(pub String);
impl JsonSchema for LanguageModelProviderSetting {
fn schema_name() -> Cow<'static, str> {
"LanguageModelProviderSetting".into()
}
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
// list the builtin providers as a subset so that we still auto complete them in the settings
json_schema!({
"anyOf": [
{
"type": "string",
"enum": [
"amazon-bedrock",
"anthropic",
"copilot_chat",
"deepseek",
"google",
"lmstudio",
"mistral",
"ollama",
"openai",
"openrouter",
"vercel",
"x_ai",
"zed.dev"
]
},
{
"type": "string",
}
]
})
}
}
impl From<String> for LanguageModelProviderSetting {
fn from(provider: String) -> Self {
Self(provider)
}
}
impl From<&str> for LanguageModelProviderSetting {
fn from(provider: &str) -> Self {
Self(provider.to_string())
}
}
#[with_fallible_options]
#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)]
pub struct AllAgentServersSettings {
pub gemini: Option<BuiltinAgentServerSettings>,
pub claude: Option<BuiltinAgentServerSettings>,
pub codex: Option<BuiltinAgentServerSettings>,
/// Custom agent servers configured by the user
#[serde(flatten)]
pub custom: HashMap<String, CustomAgentServerSettings>,
}
#[with_fallible_options]
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)]
pub struct BuiltinAgentServerSettings {
/// Absolute path to a binary to be used when launching this agent.
///
/// This can be used to run a specific binary without automatic downloads or searching `$PATH`.
#[serde(rename = "command")]
pub path: Option<PathBuf>,
/// If a binary is specified in `command`, it will be passed these arguments.
pub args: Option<Vec<String>>,
/// If a binary is specified in `command`, it will be passed these environment variables.
pub env: Option<HashMap<String, String>>,
/// Whether to skip searching `$PATH` for an agent server binary when
/// launching this agent.
///
/// This has no effect if a `command` is specified. Otherwise, when this is
/// `false`, Zed will search `$PATH` for an agent server binary and, if one
/// is found, use it for threads with this agent. If no agent binary is
/// found on `$PATH`, Zed will automatically install and use its own binary.
/// When this is `true`, Zed will not search `$PATH`, and will always use
/// its own binary.
///
/// Default: true
pub ignore_system_version: Option<bool>,
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
pub default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
pub default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default)]
pub favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default)]
pub default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default)]
pub favorite_config_option_values: HashMap<String, Vec<String>>,
}
#[with_fallible_options]
#[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CustomAgentServerSettings {
Custom {
#[serde(rename = "command")]
path: PathBuf,
#[serde(default)]
args: Vec<String>,
env: Option<HashMap<String, String>>,
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default)]
favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default)]
default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default)]
favorite_config_option_values: HashMap<String, Vec<String>>,
},
Extension {
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default)]
favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default)]
default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default)]
favorite_config_option_values: HashMap<String, Vec<String>>,
},
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolPermissionsContent {
/// Per-tool permission rules.
/// Keys: terminal, edit_file, delete_path, move_path, create_directory,
/// save_file, fetch, web_search
#[serde(default)]
pub tools: HashMap<Arc<str>, ToolRulesContent>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolRulesContent {
/// Default mode when no regex rules match.
/// Default: confirm
pub default_mode: Option<ToolPermissionMode>,
/// Regexes for inputs to auto-approve.
/// For terminal: matches command. For file tools: matches path. For fetch: matches URL.
/// Default: []
pub always_allow: Option<ExtendingVec<ToolRegexRule>>,
/// Regexes for inputs to auto-reject.
/// **SECURITY**: These take precedence over ALL other rules, across ALL settings layers.
/// Default: []
pub always_deny: Option<ExtendingVec<ToolRegexRule>>,
/// Regexes for inputs that must always prompt.
/// Takes precedence over always_allow but not always_deny.
/// Default: []
pub always_confirm: Option<ExtendingVec<ToolRegexRule>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolRegexRule {
/// The regex pattern to match.
#[serde(default)]
pub pattern: String,
/// Whether the regex is case-sensitive.
/// Default: false (case-insensitive)
pub case_sensitive: Option<bool>,
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
)]
#[serde(rename_all = "snake_case")]
pub enum ToolPermissionMode {
/// Auto-approve without prompting.
Allow,
/// Auto-reject with an error.
Deny,
/// Always prompt for confirmation (default behavior).
#[default]
Confirm,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_tool_default_mode_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.set_tool_default_mode("terminal", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(terminal_rules.default_mode, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_set_tool_default_mode_updates_existing() {
let mut settings = AgentSettingsContent::default();
settings.set_tool_default_mode("terminal", ToolPermissionMode::Confirm);
settings.set_tool_default_mode("terminal", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(terminal_rules.default_mode, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_set_tool_default_mode_for_mcp_tool() {
let mut settings = AgentSettingsContent::default();
settings.set_tool_default_mode("mcp:github:create_issue", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let mcp_rules = tool_permissions
.tools
.get("mcp:github:create_issue")
.unwrap();
assert_eq!(mcp_rules.default_mode, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_add_tool_allow_pattern_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 1);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
}
#[test]
fn test_add_tool_allow_pattern_appends_to_existing() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^npm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 2);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
assert_eq!(always_allow.0[1].pattern, "^npm\\s");
}
#[test]
fn test_add_tool_allow_pattern_does_not_duplicate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(
always_allow.0.len(),
1,
"Duplicate patterns should not be added"
);
}
#[test]
fn test_add_tool_allow_pattern_for_different_tools() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("fetch", "^https?://github\\.com".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(
terminal_rules.always_allow.as_ref().unwrap().0[0].pattern,
"^cargo\\s"
);
let fetch_rules = tool_permissions.tools.get("fetch").unwrap();
assert_eq!(
fetch_rules.always_allow.as_ref().unwrap().0[0].pattern,
"^https?://github\\.com"
);
}
#[test]
fn test_add_tool_deny_pattern_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 1);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
}
#[test]
fn test_add_tool_deny_pattern_appends_to_existing() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^sudo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 2);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
assert_eq!(always_deny.0[1].pattern, "^sudo\\s");
}
#[test]
fn test_add_tool_deny_pattern_does_not_duplicate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(
always_deny.0.len(),
1,
"Duplicate patterns should not be added"
);
}
#[test]
fn test_add_tool_deny_and_allow_patterns_separate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 1);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 1);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
}
}