fix(agent): clean removed bundled subagents

This commit is contained in:
jxxghp
2026-10-17 09:09:33 +08:00
parent f70578f279
commit 0658c7f384
2 changed files with 66 additions and 1 deletions

View File

@@ -34,6 +34,19 @@ SUBAGENT_SCHEMA_VERSION = 1
DEFAULT_PERSONA_ID = "default"
PERSONA_ID_PATTERN = re.compile(r"^[a-z0-9][a-z0-9_-]{0,63}$")
# 这些 ID 曾随旧版内置目录发布;保留迁移清单可在升级时删除旧副本,同时不影响
# 用户自行创建的其它 ID。内置 ID 视为保留名称,用户定义应使用不同的 ID。
REMOVED_BUNDLED_SUBAGENT_IDS = frozenset(
{
"download-diagnostician",
"media-researcher",
"moviepilot-explorer",
"resource-searcher",
"subscription-analyst",
"system-diagnostician",
}
)
def _default_agent_root_dir() -> Path:
"""从组合根设置服务取得 Agent 目录,导入早期保留旧设置回退。"""
@@ -475,7 +488,7 @@ class AgentRuntimeManager:
return tuple(entries)
def _sync_bundled_defaults(self) -> None:
"""同步默认运行时文件,并按版本更新内置子代理定义。"""
"""同步默认运行时文件,并按版本更新或清理内置子代理定义。"""
if not self.bundled_defaults_dir.exists():
return
for path in sorted(self.bundled_defaults_dir.rglob("*")):
@@ -492,6 +505,29 @@ class AgentRuntimeManager:
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, target)
logger.info(f"已同步默认 Agent 运行时文件: {target}")
self._remove_removed_bundled_subagents()
def _remove_removed_bundled_subagents(self) -> None:
"""删除已从内置目录移除的保留子代理,保留其它用户定义。"""
bundled_subagents_root = self.bundled_defaults_dir / SUBAGENTS_DIR
bundled_ids = (
{path.name for path in bundled_subagents_root.iterdir() if path.is_dir()}
if bundled_subagents_root.is_dir()
else set()
)
for subagent_id in sorted(REMOVED_BUNDLED_SUBAGENT_IDS - bundled_ids):
target_dir = self.subagents_dir / subagent_id
target_file = target_dir / SUBAGENT_FILE
if not target_file.is_file() and not target_file.is_symlink():
continue
target_file.unlink()
try:
target_dir.rmdir()
except OSError:
# 用户可能在保留目录中放置了其它文件,只移除旧的内置定义。
pass
logger.info(f"已删除废弃的默认 Agent 子代理定义: {target_file}")
@classmethod
def _should_update_bundled_subagent(

View File

@@ -145,6 +145,35 @@ def test_runtime_updates_bundled_subagent_when_version_increases(tmp_path):
assert "version two" in subagent.text
def test_runtime_removes_removed_bundled_subagents_and_keeps_custom_definitions(tmp_path):
"""内置子代理删除后应清理旧副本,同时保留未知的用户定义。"""
defaults_root = tmp_path / "defaults"
_write_current_persona(defaults_root)
_write_subagent(defaults_root, "general-purpose")
runtime_root = tmp_path / "agent" / "runtime"
_write_subagent(runtime_root, "download-diagnostician")
_write_subagent(
runtime_root,
"custom-reader",
description="Custom reader subagent.",
body="Only inspect custom media signals.",
)
manager = AgentRuntimeManager(
agent_root_dir=tmp_path / "agent",
bundled_defaults_dir=defaults_root,
)
runtime_config = manager.load_runtime_config()
assert not (runtime_root / "subagents" / "download-diagnostician").exists()
assert (runtime_root / "subagents" / "custom-reader" / "SUBAGENT.md").exists()
assert [subagent.subagent_id for subagent in runtime_config.available_subagents] == [
"custom-reader",
"general-purpose",
]
def test_middleware_profiles_are_loaded_from_runtime_config(monkeypatch, tmp_path):
"""子代理中间件应从运行时 YAML 定义生成 profile。"""
defaults_root = tmp_path / "defaults"