diff --git a/app/agent/prompt/System Core Prompt.txt b/app/agent/prompt/System Core Prompt.txt index 874bc960..732e7b70 100644 --- a/app/agent/prompt/System Core Prompt.txt +++ b/app/agent/prompt/System Core Prompt.txt @@ -64,7 +64,7 @@ You act as a proactive agent. Your goal is to fully resolve the user's media-rel - For fuzzy torrent names, filenames, or manually provided paths, prefer `recognize_media` before asking the user for a cleaner title. - If `search_media` fails, fall back to `search_web` or `recognize_media`. Only ask the user when automated paths are exhausted. - If torrent search yields no useful result, check site scope, site health, and recognition quality before concluding that the resource is unavailable. -- Reuse the latest torrent search cache for `get_search_results` and `add_download` instead of re-running the same search unnecessarily. +- Reuse the latest torrent search cache for `get_search_results` and `add_download_tasks` instead of re-running the same search unnecessarily. - Use `execute_command` only for diagnostics, read-only inspection, or commands the user explicitly asked to run. Its default `action=start` starts a managed background session and returns `session_id`, `status`, `last_seq`, and `output_until_seq`; call the same tool again with `action=read`, `action=wait`, `action=write`, or `action=kill` to poll output, wait in short segments, send stdin, or stop the process. diff --git a/app/agent/tools/factory.py b/app/agent/tools/factory.py index 2597169c..a15bfa6c 100644 --- a/app/agent/tools/factory.py +++ b/app/agent/tools/factory.py @@ -1,6 +1,6 @@ from typing import List, Callable -from app.agent.tools.impl.add_download import AddDownloadTool +from app.agent.tools.impl.add_download_tasks import AddDownloadTasksTool from app.agent.tools.impl.add_subscribe import AddSubscribeTool from app.agent.tools.impl.update_subscribe import UpdateSubscribeTool from app.agent.tools.impl.search_subscribe import SearchSubscribeTool @@ -50,7 +50,7 @@ from app.agent.tools.impl.query_personas import QueryPersonasTool from app.agent.tools.impl.switch_persona import SwitchPersonaTool from app.agent.tools.impl.update_persona_definition import UpdatePersonaDefinitionTool from app.agent.tools.impl.update_site_cookie import UpdateSiteCookieTool -from app.agent.tools.impl.delete_download import DeleteDownloadTool +from app.agent.tools.impl.delete_download_tasks import DeleteDownloadTasksTool from app.agent.tools.impl.delete_download_history import DeleteDownloadHistoryTool from app.agent.tools.impl.delete_transfer_history import DeleteTransferHistoryTool from app.agent.tools.impl.update_download_tasks import UpdateDownloadTasksTool @@ -167,7 +167,7 @@ class MoviePilotToolFactory: GetSearchResultsTool, SearchWebTool, RecognizeCaptchaTool, - AddDownloadTool, + AddDownloadTasksTool, QuerySubscribesTool, QuerySubscribeSharesTool, QueryPopularSubscribesTool, @@ -183,7 +183,7 @@ class MoviePilotToolFactory: QuerySubscribeHistoryTool, DeleteSubscribeTool, QueryDownloadTasksTool, - DeleteDownloadTool, + DeleteDownloadTasksTool, DeleteDownloadHistoryTool, DeleteTransferHistoryTool, UpdateDownloadTasksTool, diff --git a/app/agent/tools/impl/add_download.py b/app/agent/tools/impl/add_download_tasks.py similarity index 95% rename from app/agent/tools/impl/add_download.py rename to app/agent/tools/impl/add_download_tasks.py index e526c9e9..8fce4a23 100644 --- a/app/agent/tools/impl/add_download.py +++ b/app/agent/tools/impl/add_download_tasks.py @@ -1,28 +1,29 @@ -"""添加下载工具""" +"""添加下载任务工具""" import re from pathlib import Path -from typing import List, Optional, Type +from typing import List, Optional, Type, Union from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool from app.agent.tools.tags import ToolTag +from app.chain.download import DownloadChain from app.chain.media import MediaChain from app.chain.search import SearchChain -from app.chain.download import DownloadChain from app.core.config import settings from app.core.context import Context from app.core.metainfo import MetaInfo from app.db.site_oper import SiteOper from app.helper.directory import DirectoryHelper from app.log import logger -from app.schemas import TorrentInfo, FileURI +from app.schemas import FileURI, TorrentInfo from app.utils.crypto import HashUtils -class AddDownloadInput(BaseModel): - """添加下载工具的输入参数模型""" +class AddDownloadTasksInput(BaseModel): + """添加下载任务工具的输入参数模型""" + explanation: Optional[str] = Field(None, description="Clear explanation of why this tool is being used in the current context") torrent_url: List[str] = Field( ..., @@ -36,15 +37,17 @@ class AddDownloadInput(BaseModel): description="Comma-separated list of labels/tags to assign to the download (optional, e.g., 'movie,hd,bluray')") -class AddDownloadTool(MoviePilotTool): - name: str = "add_download" +class AddDownloadTasksTool(MoviePilotTool): + """添加下载任务工具""" + + name: str = "add_download_tasks" tags: list[str] = [ ToolTag.Write, ToolTag.Download, ToolTag.Resource, ] description: str = "Add torrent download tasks using refs from get_search_results or magnet links." - args_schema: Type[BaseModel] = AddDownloadInput + args_schema: Type[BaseModel] = AddDownloadTasksInput def get_tool_message(self, **kwargs) -> Optional[str]: """根据下载参数生成友好的提示消息""" @@ -157,16 +160,16 @@ class AddDownloadTool(MoviePilotTool): prefix = "添加种子任务失败:" if normalized_error.startswith(prefix): normalized_error = normalized_error[len(prefix):].lstrip() - if AddDownloadTool._is_magnet_link_input(normalized_error): + if AddDownloadTasksTool._is_magnet_link_input(normalized_error): normalized_error = "" if normalized_error: return f"{torrent_ref} {normalized_error}" - if AddDownloadTool._is_torrent_ref(torrent_ref): + if AddDownloadTasksTool._is_torrent_ref(torrent_ref): return torrent_ref return "" @classmethod - def _normalize_torrent_urls(cls, torrent_url: Optional[List[str] | str]) -> List[str]: + def _normalize_torrent_urls(cls, torrent_url: Optional[Union[List[str], str]]) -> List[str]: """统一规范 torrent_url 输入,保留所有非空值""" if torrent_url is None: return [] @@ -234,6 +237,7 @@ class AddDownloadTool(MoviePilotTool): async def run(self, torrent_url: Optional[List[str]] = None, downloader: Optional[str] = None, save_path: Optional[str] = None, labels: Optional[str] = None, **kwargs) -> str: + """执行添加下载任务。""" logger.info( f"执行工具: {self.name}, 参数: torrent_url={torrent_url}, downloader={downloader}, save_path={save_path}, labels={labels}") diff --git a/app/agent/tools/impl/delete_download.py b/app/agent/tools/impl/delete_download_tasks.py similarity index 92% rename from app/agent/tools/impl/delete_download.py rename to app/agent/tools/impl/delete_download_tasks.py index 152e86e4..beac5f42 100644 --- a/app/agent/tools/impl/delete_download.py +++ b/app/agent/tools/impl/delete_download_tasks.py @@ -10,7 +10,7 @@ from app.chain.download import DownloadChain from app.log import logger -class DeleteDownloadInput(BaseModel): +class DeleteDownloadTasksInput(BaseModel): """删除下载任务工具的输入参数模型""" explanation: Optional[str] = Field(None, @@ -28,15 +28,17 @@ class DeleteDownloadInput(BaseModel): ) -class DeleteDownloadTool(MoviePilotTool): - name: str = "delete_download" +class DeleteDownloadTasksTool(MoviePilotTool): + """删除下载任务工具""" + + name: str = "delete_download_tasks" tags: list[str] = [ ToolTag.Write, ToolTag.Download, ToolTag.Admin, ] description: str = "Delete a download task from the downloader by task hash only. Optionally specify the downloader name and whether to delete downloaded files." - args_schema: Type[BaseModel] = DeleteDownloadInput + args_schema: Type[BaseModel] = DeleteDownloadTasksInput require_admin: bool = True def get_tool_message(self, **kwargs) -> Optional[str]: @@ -69,6 +71,7 @@ class DeleteDownloadTool(MoviePilotTool): delete_files: Optional[bool] = False, **kwargs, ) -> str: + """执行删除下载任务。""" logger.info( f"执行工具: {self.name}, 参数: hash={hash}, downloader={downloader}, delete_files={delete_files}" ) diff --git a/docs/mcp-api.md b/docs/mcp-api.md index 04102ba5..6911cfc4 100644 --- a/docs/mcp-api.md +++ b/docs/mcp-api.md @@ -238,8 +238,10 @@ MoviePilot 也提供普通 REST API 给前端和自动化客户端使用。所 **下载任务工具说明**: +- `add_download_tasks` 用于添加下载任务,支持 `get_search_results` 返回的 `hash:id` 引用和磁力链接,可指定下载器、保存目录和标签。 - `query_download_tasks` 用于查询下载任务,支持按下载器、状态、Hash、标题、标签过滤;返回保存目录、内容路径、上传/下载速度、上传/下载限速、分类、分享率、做种时间等下载器可提供的字段。按 `hash` 查询或传入 `include_trackers=true` 时,会尽量返回 Tracker URL 列表。 - `update_download_tasks` 用于修改下载任务,统一支持 `start`/`stop`、标签、上传/下载限速、Tracker、保存目录、分类、分享率、做种时间等字段;具体字段是否成功取决于下载器能力,返回结果会按操作项逐条标记成功或失败。 +- `delete_download_tasks` 用于删除下载任务,按任务 Hash 操作,可指定下载器,并可选择是否同时删除已下载文件。 ### 3. 获取工具详情 diff --git a/skills/moviepilot-cli/SKILL.md b/skills/moviepilot-cli/SKILL.md index ebd6b01c..01931657 100644 --- a/skills/moviepilot-cli/SKILL.md +++ b/skills/moviepilot-cli/SKILL.md @@ -24,7 +24,7 @@ Always run `show ` before calling a command — parameter names are not |---|---| | Media Search | search_media, recognize_media, query_media_detail, get_recommendations, search_person, search_person_credits | | Torrent | search_torrents, get_search_results | -| Download | add_download, query_download_tasks, update_download_tasks, delete_download, query_downloaders | +| Download | add_download_tasks, query_download_tasks, update_download_tasks, delete_download_tasks, query_downloaders | | Subscription | add_subscribe, query_subscribes, update_subscribe, delete_subscribe, search_subscribe, query_subscribe_history, query_popular_subscribes, query_subscribe_shares | | Library | query_library_exists, query_library_latest, transfer_file, scrape_metadata, query_transfer_history | | Files | list_directory, query_directory_settings | @@ -95,7 +95,7 @@ If the media already exists in the library or is already subscribed, **stop** an #### 6. Add download Download one or more torrents (`torrent_url` comes from `get_search_results` output): -`node scripts/mp-cli.js add_download torrent_url="abc1234:1,def5678:2"` +`node scripts/mp-cli.js add_download_tasks torrent_url="abc1234:1,def5678:2"` #### Error handling @@ -104,7 +104,7 @@ Download one or more torrents (`torrent_url` comes from `get_search_results` out | `search_media` empty | Retry with alternative title (English/original), inform user. Still empty → ask for title or TMDB ID. | | `search_torrents` empty | Inform user, ask whether to retry with different sites. | | `get_search_results` empty | Do not silently broaden filters. Suggest which filter to relax, ask before retrying. | -| `add_download` fails | Run `query_downloaders` + `query_download_tasks` to diagnose, then report to user. | +| `add_download_tasks` fails | Run `query_downloaders` + `query_download_tasks` to diagnose, then report to user. | ### Add Subscription @@ -135,10 +135,10 @@ Add trackers to a download task: `node scripts/mp-cli.js update_download_tasks hash= trackers='https://tracker.example/announce,udp://tracker.example:80/announce'` Delete a download task (confirm with user first — irreversible): -`node scripts/mp-cli.js delete_download hash=` +`node scripts/mp-cli.js delete_download_tasks hash=` Delete a download task and also remove its files (confirm with user first — irreversible): -`node scripts/mp-cli.js delete_download hash= delete_files=true` +`node scripts/mp-cli.js delete_download_tasks hash= delete_files=true` ### Manage Subscriptions diff --git a/tests/test_agent_download_task_tool_names.py b/tests/test_agent_download_task_tool_names.py new file mode 100644 index 00000000..381aa5b7 --- /dev/null +++ b/tests/test_agent_download_task_tool_names.py @@ -0,0 +1,27 @@ +from unittest.mock import patch + +from app.agent.tools.factory import MoviePilotToolFactory + + +def test_factory_registers_plural_download_task_tool_names(): + """ + 下载任务工具应统一使用 *_download_tasks 命名。 + """ + with patch( + "app.agent.tools.factory.PluginManager.get_plugin_agent_tools", + return_value=[], + ): + tools = MoviePilotToolFactory.create_tools( + session_id="download-task-names", + user_id="10001", + ) + + tool_names = {tool.name for tool in tools} + assert { + "add_download_tasks", + "query_download_tasks", + "update_download_tasks", + "delete_download_tasks", + } <= tool_names + assert "add_download" not in tool_names + assert "delete_download" not in tool_names