feat(persona): 为 Persona 增加 Subagent 选择功能,允许指定人格可调用的 Subagent 范围,从而避免无限制调用所有已配置并启用 Subagent,降低权限风险#6906
feat(persona): 为 Persona 增加 Subagent 选择功能,允许指定人格可调用的 Subagent 范围,从而避免无限制调用所有已配置并启用 Subagent,降低权限风险#6906Astral-Yang wants to merge 6 commits intoAstrBotDevs:masterfrom
Conversation
- 在 Persona 数据库模型中新增 subagents 字段,支持选择特定 Subagents - 在仪表板人格表单中添加 Subagents 选择面板,支持搜索和批量选择 - 在人格卡片和详情页中显示 Subagents 配置状态和数量 - 在核心代理逻辑中根据人格配置过滤可用的 Subagents - 更新所有相关 API 接口以支持 subagents 字段的增删改查 - 添加多语言支持(中文、英文、俄文)的 Subagents 相关文本 - 确保数据库迁移兼容性,自动添加 subagents 列到现有表
|
Related Documentation 1 document(s) may need updating based on files changed in this PR: AstrBotTeam's Space pr4697的改动View Suggested Changes@@ -34,6 +34,59 @@
#### 配置说明
SubAgent 的定义与 Persona 配置一致,需在配置文件中指定 tools、skills、name、description 等。
+
+**Subagent 选择功能(PR #6906)**
+
+[PR #6906](https://github.com/AstrBotDevs/AstrBot/pull/6906) 为 Persona 新增了 Subagent 选择功能,允许管理员为每个人格指定可调用的 Subagent 范围,从而避免无限制调用所有已配置并启用的 Subagent,降低权限风险。该功能与现有的 Skill 选择功能类似,提供了精细的权限控制能力。
+
+**数据库支持:**
+- **新增字段**:Persona 数据库模型(`Persona` 表)新增 `subagents` 字段
+ - **字段类型**:`list | None`(JSON 格式存储)
+ - **默认值**:`None`(表示使用所有可用 Subagents)
+ - **语义说明**:
+ - `None`:使用所有可用 Subagents(默认行为)
+ - 空列表 `[]`:不使用任何 Subagent
+ - 非空列表:仅使用列表中指定的 Subagents
+
+**核心逻辑:**
+- **Subagent 过滤**(`astr_main_agent.py`):在主代理构建过程中,根据 Persona 的 `subagents` 配置过滤可用的 Subagents
+ - 如果 `subagents` 为 `None`,保留所有已启用的 Subagents
+ - 如果 `subagents` 为空列表,不加载任何 Subagent
+ - 如果 `subagents` 包含具体名称,仅加载匹配的 Subagents
+- **Handoff 工具过滤**:系统会根据 Persona 配置自动过滤 `transfer_to_*` 工具
+ - 如果 `subagents` 配置为空列表,不挂载任何 handoff 工具
+ - 如果 `subagents` 配置为非空列表,仅挂载匹配的 handoff 工具(通过移除 `transfer_to_` 前缀进行名称匹配)
+ - 如果 `subagents` 为 `None` 或未配置,挂载所有 handoff 工具
+
+**API 支持:**
+- 所有 Persona 相关 API 端点(`/api/persona`)现已支持 `subagents` 字段
+ - **创建人格**(POST `/api/persona/create`):可在请求中指定 `subagents` 字段
+ - **更新人格**(POST `/api/persona/update`):可更新 `subagents` 字段
+ - **查询人格**(GET `/api/persona/list` 和 `/api/persona/detail`):返回结果包含 `subagents` 字段
+
+**UI 集成:**
+- **Persona 表单**(`PersonaForm.vue`)新增 Subagents 选择面板
+ - **选择模式**:
+ - "默认使用全部 Subagents":设置 `subagents` 为 `None`,允许使用所有已启用的 Subagents
+ - "选择指定 Subagents":手动选择特定 Subagents,设置 `subagents` 为指定名称列表
+ - **搜索功能**:支持按名称或描述搜索 Subagents
+ - **虚拟滚动**:采用虚拟滚动列表优化大量 Subagents 场景下的性能
+ - **批量选择**:支持通过复选框批量选择/取消选择 Subagents
+ - **Chip 展示**:已选择的 Subagents 以 Chip 形式展示,支持单独移除
+- **Persona 卡片**(`PersonaCard.vue`)显示 Subagents 配置状态
+ - 显示已选择的 Subagents 数量或"全部可用"状态
+ - 使用绿色 Chip 表示"使用所有 Subagents",蓝色 Chip 表示具体数量
+- **国际化支持**:新增中文、英文、俄文三语言支持,包含所有 Subagents 相关 UI 文本
+
+**使用场景:**
+- **权限隔离**:为不同权限等级的 Persona 配置不同的 Subagent 访问权限
+- **功能分组**:根据任务类型为 Persona 分配特定的 Subagent 组合
+- **安全控制**:限制敏感 Subagent(如数据库操作、文件管理等)的访问范围,仅授权给特定 Persona
+- **场景定制**:为特定应用场景(如客服、技术支持等)配置专用的 Subagent 集合
+
+**行为变更:**
+- **修复前**:Persona 可无限制调用所有已配置并启用的 Subagents,存在权限泄露风险
+- **修复后**:管理员可为每个 Persona 精确指定可调用的 Subagent 范围,实现细粒度权限控制
**架构重构(PR #5722)**
Note: You must be authenticated to accept/decline updates. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new feature that allows users to specify which subagents a persona can access. This enhances security by preventing personas from indiscriminately calling all available subagents, and improves the control over persona behavior. The changes include modifications to the database model, dashboard interface, core agent logic, and API endpoints to fully support the new subagent selection functionality. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- In
astr_main_agent._ensure_persona_and_skills, the subagent filtering semantics differ betweenagentsandhandoffs: an emptysubagentslist blocks all agents butnot sub_agents_cfgin the handoff section allows all tools; consider aligning this so thatNonemeans all subagents and[]means no subagents for both agents and handoffs. - In
PersonaForm.vue::loadSubagents,payloadis treated as both an object (payload.main_enable,payload.agents) and potentially an array (Array.isArray(payload)), which makes the response handling brittle; it would be clearer to branch explicitly based on the actual expected API shape and avoid checkingmain_enableon a value that might be an array. - The
subagentMainEnableflag loaded inPersonaForm.vueis not used to control the UI or behavior (e.g., disabling the subagent selector when subagents are globally disabled); consider wiring this flag into the template/logic so the form reflects the global subagent enablement state.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `astr_main_agent._ensure_persona_and_skills`, the subagent filtering semantics differ between `agents` and `handoffs`: an empty `subagents` list blocks all agents but `not sub_agents_cfg` in the handoff section allows all tools; consider aligning this so that `None` means all subagents and `[]` means no subagents for both agents and handoffs.
- In `PersonaForm.vue::loadSubagents`, `payload` is treated as both an object (`payload.main_enable`, `payload.agents`) and potentially an array (`Array.isArray(payload)`), which makes the response handling brittle; it would be clearer to branch explicitly based on the actual expected API shape and avoid checking `main_enable` on a value that might be an array.
- The `subagentMainEnable` flag loaded in `PersonaForm.vue` is not used to control the UI or behavior (e.g., disabling the subagent selector when subagents are globally disabled); consider wiring this flag into the template/logic so the form reflects the global subagent enablement state.
## Individual Comments
### Comment 1
<location path="astrbot/core/astr_main_agent.py" line_range="390-399" />
<code_context>
assigned_tools: set[str] = set()
agents = orch_cfg.get("agents", [])
+
+ # 1. 提取白名单
+ sub_agents_cfg = (persona or {}).get("subagents")
+ persona_subagents = (
</code_context>
<issue_to_address>
**issue (bug_risk):** Empty `subagents` list semantics are inconsistent between agent filtering and handoff tools, which can lead to handoffs to agents that were filtered out.
`sub_agents_cfg` and `persona_subagents` are interpreted inconsistently:
- In agent filtering, any non-`None` `sub_agents_cfg` (including `[]`) yields a non-`None` `persona_subagents`, so `[]` means "no agents".
- In handoff tools, `if not sub_agents_cfg:` makes both `None` and `[]` mean "all handoffs".
So `subagents: []` clears the agent list but still enables all handoff tools, allowing transfers to agents that were filtered out.
To avoid this, use a single convention in both places: `None` = no filtering / all agents & all handoffs; `[]` = filter out all agents & no handoffs.
</issue_to_address>
### Comment 2
<location path="astrbot/core/astr_main_agent.py" line_range="398-404" />
<code_context>
+ else None
+ )
+
+ # 2. 过滤 agents
+ if persona_subagents is not None:
+ agents = [
+ agent
+ for agent in agents
+ if isinstance(agent, dict)
+ and str(agent.get("name", "")).strip() in persona_subagents
+ ]
if isinstance(agents, list):
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Subagent name normalization is applied to agent filtering but not to handoff tool matching, which can lead to subtle mismatches.
For filtering you normalize agent names with `str(...).strip()`, but handoff tools use `short_name = replace("transfer_to_", "")` and check `if short_name in sub_agents_cfg` against the unnormalized list. This inconsistency means agents may be filtered correctly but fail handoff (or vice versa) if casing/whitespace or types differ.
Consider normalizing once, e.g. `normalized_subagents = {str(name).strip() for name in sub_agents_cfg or []}`, and use that both for filtering and for handoff checks (`short_name.strip() in normalized_subagents`).
Suggested implementation:
```python
agents = orch_cfg.get("agents", [])
# 1. 提取白名单(归一化 subagents 名称)
sub_agents_cfg = (persona or {}).get("subagents")
normalized_subagents = (
{str(name).strip() for name in sub_agents_cfg if str(name).strip()}
if sub_agents_cfg is not None
else None
)
# 2. 过滤 agents(使用归一化后的名称)
if normalized_subagents is not None:
agents = [
agent
for agent in agents
if isinstance(agent, dict)
and str(agent.get("name", "")).strip() in normalized_subagents
]
```
```python
# add subagent handoff tools
# 如果 sub_agents_cfg 为 None 或空列表,则默认放行所有 handoffs
# 注意:handoff 也使用归一化后的 subagent 名称,以避免大小写/空白差异
if not sub_agents_cfg or normalized_subagents is None:
# 不配置 subagents 时,默认放行所有 handoffs
for tool in so.handoffs:
req.func_tool.register(tool)
else:
# 只允许指向归一化白名单中的 subagents 的 handoff
for tool in so.handoffs:
short_name = (getattr(tool, "name", None) or "").replace("transfer_to_", "").strip()
if short_name in normalized_subagents:
req.func_tool.register(tool)
```
I assumed the handoff registration was a simple `for tool in so.handoffs: req.func_tool.register(tool)` plus a membership check against `sub_agents_cfg`. If the actual body under `for tool in so.handoffs:` is more complex (e.g., additional conditions or different registration method), you should:
1. Move that existing registration logic into both branches (the “allow all” branch and the filtered branch) instead of the simple `req.func_tool.register(tool)` used above.
2. In the filtered branch, keep everything you already do, but add the `short_name` normalization and `if short_name in normalized_subagents:` guard around the registration logic.
3. Ensure `normalized_subagents` is in scope where handoffs are configured (same function or enclosing scope).
</issue_to_address>
### Comment 3
<location path="dashboard/src/components/shared/PersonaForm.vue" line_range="709-718" />
<code_context>
+ async loadSubagents(){
</code_context>
<issue_to_address>
**issue (bug_risk):** The `loadSubagents` response handling mixes object and array shapes in a way that can silently ignore valid data.
Because `payload` is treated as both an object and an array, a plain array response with valid subagents but no `main_enable` will set `subagentMainEnable` to `undefined` and skip population entirely. Similarly, small API shape changes could bypass the intended array branch.
Consider first determining whether the response is an object-with-config or a bare array, then:
- Derive `subagentMainEnable` explicitly from the config shape only, and
- Derive `subagents` from either the array or the object’s `agents` field,
without ever reading `payload.main_enable` when `payload` is an array.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- 将 PersonaCard 中的子代理图标从闪电改为链接图标以提升语义一致性 - 移除 PersonaForm 中冗余的子代理主开关检查,直接使用配置中的代理列表 - 修复主代理中子代理白名单处理逻辑,确保名称归一化并正确处理空集合情况
There was a problem hiding this comment.
Code Review
This pull request introduces subagent management for personas, allowing users to define which subagents a persona can utilize. This includes database schema updates, API modifications for persona creation and updates, and a new UI component for subagent selection within the persona form. The review identified several issues: a logical error in astr_main_agent.py where an empty subagent list was incorrectly treated as "all subagents" and a performance concern with in checks on lists; a similar logical error in persona.py where an empty subagent list was converted to None; and a potential bug in PersonaForm.vue where subagent loading logic might fail if the API response format is an array instead of an object. Additionally, a code duplication issue was noted in PersonaForm.vue regarding removeSubagent and toggleSubagent methods.
I am having trouble creating individual review comments. Click here to see my feedback.
astrbot/core/astr_main_agent.py (441-450)
这部分逻辑存在两个问题:
- 正确性问题:
if not sub_agents_cfg:会在sub_agents_cfg为[](空列表) 时返回True,从而错误地允许所有handoffs。根据设计,空列表应该表示“不允许任何 subagent”,而不是“允许所有”。这可能会导致权限控制失效。 - 性能和一致性问题:在
else分支中,代码使用了sub_agents_cfg(一个列表) 进行in操作,这是 O(n) 的线性搜索。而在前面已经创建了一个经过清理的、用于 O(1) 查找的set,即persona_subagents。这里应该复用persona_subagents以提高性能并确保逻辑一致性(例如,处理名称前后的空格)。
建议重构此部分,使用 persona_subagents 来进行判断和过滤。
if persona_subagents is None:
for tool in so.handoffs:
req.func_tool.add_tool(tool)
else:
for tool in so.handoffs:
# 去掉 "transfer_to_" 前缀再匹配
short_name = tool.name.replace("transfer_to_", "")
if short_name in persona_subagents:
req.func_tool.add_tool(tool)
astrbot/dashboard/routes/persona.py (160)
这里的 subagents if subagents else None 逻辑会将一个空列表 [] 转换为 None。根据设计,[] 表示不允许任何 subagents,而 None 表示允许所有 subagents。这个转换会导致无法创建一个不允许任何 subagents 的 persona。
为了正确处理空列表,应该直接传递 subagents 变量,因为 data.get("subagents") 在键不存在时会返回 None,这已经能正确处理 subagents 未提供的情况。
同样的问题也可能存在于 tools 和 skills 的处理中。
subagents=subagents,
dashboard/src/components/shared/PersonaForm.vue (713-726)
加载 subagents 的逻辑似乎过于复杂,并且存在一个潜在的错误。如果 /api/subagent/config 返回一个数组,payload.main_enable 将是 undefined,导致 if (this.subagentMainEnable) 为 false,因此不会加载任何 subagents。if (Array.isArray(payload)) 代码块实际上是无法访问的死代码。
为了简化和修复这个问题,可以使此逻辑与 loadSkills 保持一致,即假设 API 返回一个包含 main_enable 和 agents 属性的对象。这将使代码更具可读性和健壮性。
if (response.data.status === 'ok') {
const payload = response.data.data || {};
this.subagentMainEnable = payload.main_enable;
if (this.subagentMainEnable) {
const subagents = payload.agents || [];
this.availableSubagents = subagents.filter(subagent => subagent.enabled !== false);
}
} else {
this.$emit('error', response.data.message || 'Failed to load subagents');
}
dashboard/src/components/shared/PersonaForm.vue (951-962)
removeSubagent 方法中的逻辑与 toggleSubagent 方法有大量重复。为了减少重复代码并提高可维护性,你可以让 removeSubagent 复用 toggleSubagent 的逻辑。
由于 removeSubagent 的作用是取消选择一个 subagent,你可以先检查它是否已被选择,如果是,则调用 toggleSubagent 来取消它。
removeSubagent(subagentName) {
if (this.isSubagentSelected(subagentName)) {
this.toggleSubagent(subagentName);
}
},
|
@gemini-code-assist review |
There was a problem hiding this comment.
Code Review
This pull request introduces the concept of 'subagents' into the persona management system. It updates the database schema to include a subagents field for personas, modifies core agent logic to filter agents and handoff tools based on persona-defined subagent whitelists, and extends the API and manager functions to support subagent configuration. The frontend UI has also been updated with a new 'Subagents Selection' panel in the persona form, including search, selection, and display functionalities, along with internationalization. The review highlights several critical logical errors in the persona creation and retrieval process related to the handling of None versus empty lists for tools, skills, and subagents, which can lead to incorrect semantic interpretation. Additionally, it suggests improving the robustness of handoff tool name parsing in the core agent logic and refactoring duplicated code in the frontend for subagent selection to enhance maintainability and state consistency.
- 之前的实现错误地尝试从工具名称中提取代理名,现在直接使用工具的agent.name属性进行白名单检查,提升健壮性。
移除_ensure_persona_and_skills方法中冗余的调试日志输出,该日志仅用于打印代理名称,不影响功能逻辑。
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
dashboard/src/components/shared/PersonaForm.vuethesubagentMainEnabledata property is defined but never used; consider removing it or wiring it into the UI logic if it was intended to control subagent-related behavior. - In
astrbot/dashboard/routes/persona.py#create_persona,subagents(liketools/skills) is passed assubagents if subagents else None, which collapses an explicit empty list ("no subagents allowed") intoNone("all subagents allowed"); if you want to preserve the allowlist semantics you defined, passsubagentsthrough unchanged and letNonevs[]be distinguished.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `dashboard/src/components/shared/PersonaForm.vue` the `subagentMainEnable` data property is defined but never used; consider removing it or wiring it into the UI logic if it was intended to control subagent-related behavior.
- In `astrbot/dashboard/routes/persona.py#create_persona`, `subagents` (like `tools`/`skills`) is passed as `subagents if subagents else None`, which collapses an explicit empty list ("no subagents allowed") into `None` ("all subagents allowed"); if you want to preserve the allowlist semantics you defined, pass `subagents` through unchanged and let `None` vs `[]` be distinguished.
## Individual Comments
### Comment 1
<location path="astrbot/core/astr_main_agent.py" line_range="438-447" />
<code_context>
req.func_tool = ToolSet()
# add subagent handoff tools
- for tool in so.handoffs:
- req.func_tool.add_tool(tool)
+ # 如果 normalized_subagents 为 None 则默认放行所有 handoffs,空集合禁用所有handoffs
+ if normalized_subagents is None:
+ # 不配置 subagents 时,默认放行所有 handoffs
+ for tool in so.handoffs:
+ req.func_tool.add_tool(tool)
+ else:
+ # 只允许指向归一化白名单中的 subagents 的 handoff
+ for tool in so.handoffs:
+ agent = getattr(tool, "agent", None)
+ agent_name = getattr(agent, "name", None) if agent else None
+ if agent_name and agent_name.strip() in normalized_subagents:
+ req.func_tool.add_tool(tool)
</code_context>
<issue_to_address>
**issue (bug_risk):** Calling `.strip()` on `agent_name` assumes it is always a string, which may not hold and could raise at runtime.
Here `agent_name` may not be a string, so `agent_name.strip()` can raise if `agent.name` is another type or doesn’t implement `.strip()`. Since `normalized_subagents` is already built via `str(name).strip()`, you can mirror that here to be safe:
```python
if agent_name is not None:
name_norm = str(agent_name).strip()
if name_norm and name_norm in normalized_subagents:
req.func_tool.add_tool(tool)
```
This keeps the normalization consistent and avoids type-related runtime errors.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- 修复子代理工具匹配逻辑中可能因agent_name类型是导致的匹配问题 - 移除前端未使用的subagentMainEnable状态变量以简化代码。
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The
PersonaForm.vuemethodstoggleSubagentandremoveSubagentcontain very similar branches (especially for thesubagents === nullcase); consider refactoring the shared logic into a small helper to simplify future changes and keep the tri‑state behavior (null/[]/list) easier to reason about. - In
PersonaForm.vueyou define.subagents-selectiontwice with differentmax-heightvalues (300px and 38vh); aligning these into a single rule or clarifying which one is intended will avoid confusing cascade behavior across different viewport sizes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `PersonaForm.vue` methods `toggleSubagent` and `removeSubagent` contain very similar branches (especially for the `subagents === null` case); consider refactoring the shared logic into a small helper to simplify future changes and keep the tri‑state behavior (null/[]/list) easier to reason about.
- In `PersonaForm.vue` you define `.subagents-selection` twice with different `max-height` values (300px and 38vh); aligning these into a single rule or clarifying which one is intended will avoid confusing cascade behavior across different viewport sizes.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
问题处理完毕,如果仍然有什么问题请告诉我进行更改 |
增加 Subagent 选择功能,类似Skill选择,允许指定人格可调用的 Subagent 范围,从而避免无限制调用所有已配置并启用 Subagent,降低权限风险
Modifications / 改动点
在 Persona 数据库模型中新增 subagents 字段,支持选择特定 Subagents
在仪表板人格表单中添加 Subagents 选择面板,支持搜索和批量选择
在人格卡片和详情页中显示 Subagents 配置状态和数量
在核心代理逻辑中根据人格配置过滤可用的 Subagents
更新所有相关 API 接口以支持 subagents 字段的增删改查
添加多语言支持(中文、英文、俄文)的 Subagents 相关文本
确保数据库迁移兼容性,自动添加 subagents 列到现有表
This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
观察persona_toolset
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
[x 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Add persona-level configuration for allowed subagents and integrate it through the backend, database, and dashboard UI.
New Features:
Enhancements: