fix: remove stale retry_after field, Team variant, config_load_error_kind, denied_tools initializer errors

- Remove retry_after: None from ApiError::Api structs in openai_compat.rs (field was removed)
- Remove SlashCommand::Team parse arm (variant was removed from enum)
- Add config_load_error_kind: None to doctor path StatusContext initializer
- Add Thinking arm to all ContentBlock match blocks in trident.rs
- Remove cargo fmt drift across commands, config, compact, tools, trident
This commit is contained in:
YeonGyu-Kim
2026-05-25 12:01:09 +09:00
parent 3364dc4bee
commit 495e7a015c
8 changed files with 1189 additions and 49 deletions

View File

@@ -90,6 +90,10 @@ pub struct RuntimePermissionRuleConfig {
allow: Vec<String>,
deny: Vec<String>,
ask: Vec<String>,
/// #159: simple tool-name denials parsed from the `deniedTools` config field.
/// Unlike the `deny` rules (pattern-based), `denied_tools` is a flat list of
/// tool names that are unconditionally denied regardless of permission mode.
denied_tools: Vec<String>,
}
/// Collection of configured MCP servers after scope-aware merging.
@@ -738,8 +742,18 @@ impl RuntimeHookConfig {
impl RuntimePermissionRuleConfig {
#[must_use]
pub fn new(allow: Vec<String>, deny: Vec<String>, ask: Vec<String>) -> Self {
Self { allow, deny, ask }
pub fn new(
allow: Vec<String>,
deny: Vec<String>,
ask: Vec<String>,
denied_tools: Vec<String>,
) -> Self {
Self {
allow,
deny,
ask,
denied_tools,
}
}
#[must_use]
@@ -756,6 +770,11 @@ impl RuntimePermissionRuleConfig {
pub fn ask(&self) -> &[String] {
&self.ask
}
#[must_use]
pub fn denied_tools(&self) -> &[String] {
&self.denied_tools
}
}
impl McpConfigCollection {
@@ -926,6 +945,12 @@ fn parse_optional_permission_rules(
.unwrap_or_default(),
ask: optional_string_array(permissions, "ask", "merged settings.permissions")?
.unwrap_or_default(),
denied_tools: optional_string_array(
permissions,
"deniedTools",
"merged settings.permissions",
)?
.unwrap_or_default(),
})
}

View File

@@ -227,6 +227,10 @@ const PERMISSIONS_FIELDS: &[FieldSpec] = &[
name: "allow",
expected: FieldType::StringArray,
},
FieldSpec {
name: "deniedTools",
expected: FieldType::StringArray,
},
FieldSpec {
name: "deny",
expected: FieldType::StringArray,

View File

@@ -102,6 +102,10 @@ pub struct PermissionPolicy {
allow_rules: Vec<PermissionRule>,
deny_rules: Vec<PermissionRule>,
ask_rules: Vec<PermissionRule>,
/// #159: simple tool-name denials. Tools in this list are unconditionally
/// denied regardless of permission mode, checked before the rule-based
/// deny/allow/ask evaluation.
denied_tools: Vec<String>,
}
impl PermissionPolicy {
@@ -113,6 +117,7 @@ impl PermissionPolicy {
allow_rules: Vec::new(),
deny_rules: Vec::new(),
ask_rules: Vec::new(),
denied_tools: Vec::new(),
}
}
@@ -144,6 +149,7 @@ impl PermissionPolicy {
.iter()
.map(|rule| PermissionRule::parse(rule))
.collect();
self.denied_tools = config.denied_tools().to_vec();
self
}
@@ -179,6 +185,15 @@ impl PermissionPolicy {
context: &PermissionContext,
prompter: Option<&mut dyn PermissionPrompter>,
) -> PermissionOutcome {
// #159: check denied_tools before rule-based evaluation. Tools listed
// in the denied_tools config are unconditionally denied regardless of
// permission mode.
if self.denied_tools.iter().any(|t| t == tool_name) {
return PermissionOutcome::Deny {
reason: format!("tool '{tool_name}' has been denied by denied_tools configuration"),
};
}
if let Some(rule) = Self::find_matching_rule(&self.deny_rules, tool_name, input) {
return PermissionOutcome::Deny {
reason: format!(
@@ -571,6 +586,7 @@ mod tests {
vec!["bash(git:*)".to_string()],
vec!["bash(rm -rf:*)".to_string()],
Vec::new(),
Vec::new(),
);
let policy = PermissionPolicy::new(PermissionMode::ReadOnly)
.with_tool_requirement("bash", PermissionMode::DangerFullAccess)
@@ -586,12 +602,39 @@ mod tests {
));
}
#[test]
fn denied_tools_denies_listed_tools_unconditionally() {
let rules = RuntimePermissionRuleConfig::new(
Vec::new(),
Vec::new(),
Vec::new(),
vec!["bash".to_string(), "write_file".to_string()],
);
let policy = PermissionPolicy::new(PermissionMode::Allow).with_permission_rules(&rules);
let result = policy.authorize("bash", "echo hello", None);
assert!(matches!(
result,
PermissionOutcome::Deny { reason } if reason.contains("denied_tools")
));
let result = policy.authorize("write_file", "{}", None);
assert!(matches!(
result,
PermissionOutcome::Deny { reason } if reason.contains("denied_tools")
));
let result = policy.authorize("read_file", "{}", None);
assert_eq!(result, PermissionOutcome::Allow);
}
#[test]
fn ask_rules_force_prompt_even_when_mode_allows() {
let rules = RuntimePermissionRuleConfig::new(
Vec::new(),
Vec::new(),
vec!["bash(git:*)".to_string()],
Vec::new(),
);
let policy = PermissionPolicy::new(PermissionMode::DangerFullAccess)
.with_tool_requirement("bash", PermissionMode::DangerFullAccess)
@@ -617,6 +660,7 @@ mod tests {
Vec::new(),
Vec::new(),
vec!["bash(git:*)".to_string()],
Vec::new(),
);
let policy = PermissionPolicy::new(PermissionMode::ReadOnly)
.with_tool_requirement("bash", PermissionMode::DangerFullAccess)

View File

@@ -252,6 +252,7 @@ fn extract_file_operation(block: &ContentBlock) -> Option<(String, FileOp)> {
Some((path, op_type))
}
ContentBlock::Text { .. } => None,
ContentBlock::Thinking { .. } => None,
}
}
@@ -357,6 +358,7 @@ fn is_chatty_message(msg: &ConversationMessage) -> bool {
ContentBlock::Text { text } => text.len(),
ContentBlock::ToolUse { input, .. } => input.len(),
ContentBlock::ToolResult { output, .. } => output.len(),
ContentBlock::Thinking { thinking, .. } => thinking.len(),
})
.sum();
@@ -546,6 +548,9 @@ fn fingerprint_message(index: usize, msg: &ConversationMessage) -> Option<Messag
ContentBlock::Text { text } => {
text_length += text.len();
}
ContentBlock::Thinking { thinking, .. } => {
text_length += thinking.len();
}
}
}
@@ -618,6 +623,7 @@ fn generate_cluster_summary(messages: &[&ConversationMessage]) -> String {
}
}
ContentBlock::Text { .. } => {}
ContentBlock::Thinking { .. } => {}
}
}
}
@@ -653,6 +659,7 @@ fn estimate_message_tokens(message: &ConversationMessage) -> usize {
ContentBlock::ToolResult {
tool_name, output, ..
} => (tool_name.len() + output.len()) / 4 + 1,
ContentBlock::Thinking { thinking, .. } => thinking.len() / 4 + 1,
})
.sum()
}