PicoBot/src/domain/mod.rs
oudecheng c27efedb6c feat(capability): 扩展 CapabilityPolicy 支持子代理黑白名单
- domain: CapabilityPolicy 新增 allowed_subagents/denied_subagents 字段及 check_subagent_allowed 方法
- experts: 专家 frontmatter 解析/渲染支持子代理策略字段
- task/runtime: spawn/resume 双路径校验父代理子代理策略;新增 update_subagent 写回 SUBAGENT.md(与 update_expert 对称);新增 SubagentPromptProvider 按专家策略过滤子代理索引
- task/runtime: 子代理自身 capability 作为孙代理的 parent_capability 透传(ToolContext),保持解耦
- traits: ToolContext 新增 parent_capability 字段
- agent_factory: 主 agent 注入 expert_capability 到 ToolContext

安全:策略不通过即拒绝(与 def 不可用即拒绝范式一致),防止 LLM 通过选择被禁子代理绕过限制;max_nesting_depth 兜底防递归不可被 def 覆盖。
2026-07-30 17:37:29 +08:00

83 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

pub mod messages;
pub mod tools;
use serde::{Deserialize, Serialize};
/// 角色能力策略:工具、技能、子代理的白/黑名单。全为空表示沿用默认(不过滤)。
///
/// 生效顺序:先白名单取交集,再黑名单扣除。专家与子代理共用此结构,
/// 确保语义一致。MCP 工具注册在 `ToolRegistry` 中(名 `mcp_*`),与内置
/// 工具同源,因此 `allowed_tools`/`denied_tools` 覆盖内置 + MCP 工具;
/// `allowed_skills`/`denied_skills` 仅覆盖 SKILL.md 技能;
/// `allowed_subagents`/`denied_subagents` 覆盖子代理加载(通过 ToolContext
/// 传递给 TaskTool在 spawn/resume 时强制校验)。
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CapabilityPolicy {
/// 技能白名单:`None` = 不限;`Some(vec)` = 仅这些 SKILL.md 技能可见。
/// `Some(vec![])` 表示全禁(与 `ToolRegistry::only` 的空交集语义对齐)。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_skills: Option<Vec<String>>,
/// 技能黑名单:禁用这些 SKILL.md 技能。空表示不禁。
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_skills: Vec<String>,
/// 工具白名单(含 `mcp_*` 工具):`None` = 不限。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_tools: Option<Vec<String>>,
/// 工具黑名单(含 `mcp_*` 工具)。
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_tools: Vec<String>,
/// 子代理白名单:`None` = 不限;`Some(vec)` = 仅这些子代理可被加载。
/// 通过 ToolContext 传递给 TaskTool在 spawn/resume 时强制校验。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_subagents: Option<Vec<String>>,
/// 子代理黑名单:禁止加载这些子代理。空表示不禁。
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_subagents: Vec<String>,
}
impl CapabilityPolicy {
/// 所有策略字段均为空 → 沿用主智能体默认配置(不过滤)。
pub fn is_empty(&self) -> bool {
self.allowed_skills.is_none()
&& self.denied_skills.is_empty()
&& self.allowed_tools.is_none()
&& self.denied_tools.is_empty()
&& self.allowed_subagents.is_none()
&& self.denied_subagents.is_empty()
}
/// 是否声明了任何技能策略。
pub fn has_skill_policy(&self) -> bool {
self.allowed_skills.is_some() || !self.denied_skills.is_empty()
}
/// 是否声明了任何工具策略。
pub fn has_tool_policy(&self) -> bool {
self.allowed_tools.is_some() || !self.denied_tools.is_empty()
}
/// 是否声明了任何子代理策略。
pub fn has_subagent_policy(&self) -> bool {
self.allowed_subagents.is_some() || !self.denied_subagents.is_empty()
}
/// 校验指定子代理是否被允许。返回 Err 时附带拒绝原因。
pub fn check_subagent_allowed(&self, name: &str) -> Result<(), String> {
if let Some(list) = &self.allowed_subagents {
if !list.iter().any(|s| s == name) {
return Err(format!(
"subagent '{}' is not in the allowed_subagents whitelist",
name
));
}
}
if self.denied_subagents.iter().any(|s| s == name) {
return Err(format!(
"subagent '{}' is in the denied_subagents blacklist",
name
));
}
Ok(())
}
}