diff --git a/app/agent/middleware/subagents.py b/app/agent/middleware/subagents.py index 529097b4..ec29695a 100644 --- a/app/agent/middleware/subagents.py +++ b/app/agent/middleware/subagents.py @@ -52,7 +52,7 @@ Delegation modes: batch and wait for the batch in one tool call. Rules: -- Delegate when a task benefits from focused investigation, such as media identity checks, site/resource search, subscription analysis, download/transfer diagnosis, or read-only system inspection. +- Delegate when a task benefits from focused investigation, such as media identity checks, site/resource search, subscription analysis, download/transfer diagnosis, MoviePilot code/config exploration, or read-only system inspection. - Subagent output is private context for your decision-making. Do not expose a subagent's process or final report verbatim to the user. - Subagents must not send messages to the user, ask for interaction, or reveal their internal tool activity. - Give the user only your synthesized final answer and the minimum necessary next step. @@ -260,6 +260,25 @@ def _builtin_subagent_profiles() -> tuple[_SubAgentProfile, ...]: ), exclude_tags=default_exclude_tags, ), + _SubAgentProfile( + name="moviepilot-explorer", + description="MoviePilot exploration subagent for source-code inspection, configuration structure analysis, logs, and code-level troubleshooting clues.", + prompt=( + f"{SUBAGENT_BASE_PROMPT}\n" + "You specialize in MoviePilot source-code structure, local configuration files, directory layout, logs or read-only command output, and code-level root-cause troubleshooting. " + "Prefer reading relevant code paths before judging behavior, and distinguish code/config evidence from runtime system state." + ), + include_tags=frozenset( + { + ToolTag.System.value, + ToolTag.Settings.value, + ToolTag.File.value, + ToolTag.Directory.value, + ToolTag.Command.value, + } + ), + exclude_tags=default_exclude_tags, + ), _SubAgentProfile( name="resource-searcher", description="Site and resource search subagent for site checks, torrent search, and resource quality analysis.", diff --git a/tests/test_agent_subagents.py b/tests/test_agent_subagents.py index 757a0363..abc0b718 100644 --- a/tests/test_agent_subagents.py +++ b/tests/test_agent_subagents.py @@ -36,6 +36,7 @@ class TestAgentSubagents(unittest.TestCase): [SUBAGENT_TASK_TOOL_NAME, SUBAGENT_CONTROL_TOOL_NAME], ) self.assertIn("media-researcher", task_tools[0].description) + self.assertIn("moviepilot-explorer", task_tools[0].description) self.assertIn("system-diagnostician", task_tools[0].description) self.assertIn("action=start", task_tools[1].description) self.assertIn("action=wait", task_tools[1].description) @@ -77,6 +78,56 @@ class TestAgentSubagents(unittest.TestCase): ["custom_media_lookup"], ) + def test_moviepilot_explorer_selects_code_and_settings_tools(self): + """MoviePilot 探索子代理应能读取代码、目录、设置和命令诊断工具。""" + model = FakeListChatModel(responses=["ok"]) + tools = [ + SimpleNamespace( + name="custom_code_reader", + tags=[ToolTag.Read.value, ToolTag.File.value], + ), + SimpleNamespace( + name="custom_directory_lister", + tags=[ToolTag.Read.value, ToolTag.Directory.value], + ), + SimpleNamespace( + name="custom_settings_reader", + tags=[ToolTag.Read.value, ToolTag.Settings.value], + ), + SimpleNamespace( + name="custom_command_runner", + tags=[ToolTag.Read.value, ToolTag.Command.value], + ), + SimpleNamespace( + name="custom_code_writer", + tags=[ToolTag.Read.value, ToolTag.Write.value, ToolTag.File.value], + ), + ] + captured = {} + + def _fake_create_agent(**kwargs): + captured.update(kwargs) + return kwargs + + middleware = MoviePilotSubAgentMiddleware( + model=model, + profiles=subagent_module._builtin_subagent_profiles(), + tools=tools, + ) + + with patch.object(subagent_module, "create_agent", side_effect=_fake_create_agent): + middleware._get_agent("moviepilot-explorer") + + self.assertEqual( + [tool.name for tool in captured["tools"]], + [ + "custom_code_reader", + "custom_directory_lister", + "custom_settings_reader", + "custom_command_runner", + ], + ) + def test_builtin_tools_declare_tags_in_implementation(self): """所有内置工具实现都应显式声明 tags。""" impl_dir = Path(__file__).resolve().parents[1] / "app" / "agent" / "tools" / "impl"