From e5722ed987c4ef3b0816b69545962b3e67a32c2a Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 1 Sep 2026 01:46:22 +0800 Subject: [PATCH] fix: finalize agent architecture ratchets --- app/agent/tools/impl/service.py | 40 +++++++++---------- app/api/endpoints/site.py | 2 +- app/api/endpoints/storage.py | 2 +- app/api/endpoints/workflow.py | 2 +- app/schemas/site.py | 2 +- docs/architecture/agent-tool-refactor-plan.md | 10 +++-- .../fixtures/architecture/mypy-baseline.json | 2 +- .../fixtures/architecture/ruff-baseline.json | 6 --- 8 files changed, 32 insertions(+), 34 deletions(-) diff --git a/app/agent/tools/impl/service.py b/app/agent/tools/impl/service.py index f505fed16..747f70d0a 100644 --- a/app/agent/tools/impl/service.py +++ b/app/agent/tools/impl/service.py @@ -17,7 +17,7 @@ from app.agent.tools.tags import ToolTag from app.runtime.settings import get_runtime_setting -class DownloaderOperationInput(BaseModel): +class DownloaderOperationInput(BaseModel): # type: ignore[misc] """下载器操作工具的运行时输入模型。""" client: Optional[str] = Field( @@ -35,7 +35,7 @@ class DownloaderOperationInput(BaseModel): model_config = ConfigDict(extra="forbid") -class MediaServerOperationInput(BaseModel): +class MediaServerOperationInput(BaseModel): # type: ignore[misc] """媒体服务器操作工具的运行时输入模型。""" server: Optional[str] = Field( @@ -53,7 +53,7 @@ class MediaServerOperationInput(BaseModel): model_config = ConfigDict(extra="forbid") -class DatabaseOperationInput(BaseModel): +class DatabaseOperationInput(BaseModel): # type: ignore[misc] """数据库操作工具的运行时输入模型。""" action: str = Field( @@ -300,7 +300,7 @@ def _parse_script_payload(stdout: str) -> dict[str, Any]: def _run_service_script( *, relative_script: str, - selector_flag: str, + selector_flag: Optional[str], selector_value: Optional[str], action: str, arguments: Dict[str, Any], @@ -317,7 +317,7 @@ def _run_service_script( "--arguments", json.dumps(arguments, ensure_ascii=False, separators=(",", ":")), ] - if selector_value: + if selector_value and selector_flag: command.extend([selector_flag, selector_value]) completed = subprocess.run( command, @@ -386,12 +386,12 @@ class DownloaderOperationTool(_ServiceOperationTool): ) tags: list[str] = [ToolTag.Download] args_schema: Type[BaseModel] = DownloaderOperationInput - _relative_script = "skills/downloader-operation/scripts/mp-downloader.py" - _selector_name = "client" - _selector_flag = "--client" - _blocking_bucket = "downloader" + _relative_script: ClassVar[str] = "skills/downloader-operation/scripts/mp-downloader.py" + _selector_name: ClassVar[Optional[str]] = "client" + _selector_flag: ClassVar[Optional[str]] = "--client" + _blocking_bucket: ClassVar[str] = "downloader" - async def run( + async def run( # type: ignore[override] self, action: str, arguments: Optional[Dict[str, Any]] = None, @@ -417,12 +417,12 @@ class MediaServerOperationTool(_ServiceOperationTool): ) tags: list[str] = [ToolTag.Media] args_schema: Type[BaseModel] = MediaServerOperationInput - _relative_script = "skills/mediaserver-operation/scripts/mp-mediaserver.py" - _selector_name = "server" - _selector_flag = "--server" - _blocking_bucket = "mediaserver" + _relative_script: ClassVar[str] = "skills/mediaserver-operation/scripts/mp-mediaserver.py" + _selector_name: ClassVar[Optional[str]] = "server" + _selector_flag: ClassVar[Optional[str]] = "--server" + _blocking_bucket: ClassVar[str] = "mediaserver" - async def run( + async def run( # type: ignore[override] self, action: str, arguments: Optional[Dict[str, Any]] = None, @@ -523,10 +523,10 @@ class DatabaseOperationTool(_ServiceOperationTool): ) tags: list[str] = [ToolTag.Admin] args_schema: Type[BaseModel] = DatabaseOperationInput - _relative_script = "skills/database-operation/scripts/mp-db.py" - _selector_name = None - _selector_flag = None - _blocking_bucket = "db" + _relative_script: ClassVar[str] = "skills/database-operation/scripts/mp-db.py" + _selector_name: ClassVar[Optional[str]] = None + _selector_flag: ClassVar[Optional[str]] = None + _blocking_bucket: ClassVar[str] = "db" def get_mcp_input_schema(self) -> dict[str, Any]: """返回数据库 action 的完整 MCP JSON Schema。""" @@ -539,7 +539,7 @@ class DatabaseOperationTool(_ServiceOperationTool): title=self.name, ) - async def run( + async def run( # type: ignore[override] self, action: str, arguments: Optional[Dict[str, Any]] = None, diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index 412d420d6..b7c4fcdf2 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -130,7 +130,7 @@ async def read_sites( return await query.list_ordered() -@router.get( +@router.get( # type: ignore[misc] "/agent", summary="查询 Agent 可用站点", response_model=List[_SchemaJsonObject], diff --git a/app/api/endpoints/storage.py b/app/api/endpoints/storage.py index da8627631..57a3298ea 100644 --- a/app/api/endpoints/storage.py +++ b/app/api/endpoints/storage.py @@ -146,7 +146,7 @@ def _list_files( return file_list or [] -@router.post( +@router.post( # type: ignore[misc] "/agent/list", summary="查询 Agent 可用目录和文件", response_model=List[_SchemaFileItem], diff --git a/app/api/endpoints/workflow.py b/app/api/endpoints/workflow.py index 9b5827e72..cb275a9f2 100644 --- a/app/api/endpoints/workflow.py +++ b/app/api/endpoints/workflow.py @@ -43,7 +43,7 @@ async def list_workflows( return await query.list() -@router.get( +@router.get( # type: ignore[misc] "/agent", summary="查询 Agent 可用工作流", response_model=List[_SchemaJsonObject], diff --git a/app/schemas/site.py b/app/schemas/site.py index e53a36543..bd1f01148 100644 --- a/app/schemas/site.py +++ b/app/schemas/site.py @@ -60,7 +60,7 @@ class Site(BaseModel): model_config = ConfigDict(from_attributes=True) -class SitePriorityUpdate(BaseModel): +class SitePriorityUpdate(BaseModel): # type: ignore[misc] """站点批量优先级更新项。""" id: int = Field(..., description="Persistent site ID returned by site.list.") diff --git a/docs/architecture/agent-tool-refactor-plan.md b/docs/architecture/agent-tool-refactor-plan.md index ad9002306..098f6ef95 100644 --- a/docs/architecture/agent-tool-refactor-plan.md +++ b/docs/architecture/agent-tool-refactor-plan.md @@ -1,6 +1,6 @@ # MoviePilot Agent 工具体系重构计划 -> 状态:IN PROGRESS — 本地验收已通过,提交推送与远端 CI 收口 +> 状态:IN PROGRESS — 全量能力与本地门禁已通过,等待最终修复提交和远端 CI 终态 > > 建立日期:2026-08-31 > @@ -41,7 +41,7 @@ | MCP/HTTP 工具管理 | 存在旧业务工具与同名 first-wins 选择空间 | 与主 Agent 共用严格唯一新目录;重名直接以 TOOL_IDENTITY_AMBIGUOUS 失败 | | 退役代码 | 旧实现仍位于 app/agent/tools/impl | 77 个退役文件已直接删除,其中 72 个工具模块、5 个辅助模块 | | 架构图 | 982 个宿主模块、8,430 条内部依赖边 | 919 个宿主模块、7,680 条内部依赖边,Application/Chain 具体 Adapter 直连仍为 0 | -| 工作区状态 | 基线提交 871632af,与 origin/v3 对齐,初始工作区干净 | 生产改动、生成合同、静态检查、全量测试和固定 80% 覆盖率门禁已完成;提交、推送和远端 CI 尚待完成 | +| 工作区状态 | 基线提交 871632af,与 origin/v3 对齐,初始工作区干净 | 主体提交 `6d4d7331d` 与复杂度修复 `28298c485` 已推送;生产改动、生成合同、静态检查、全量测试和固定 80% 覆盖率门禁已完成,正在提交 Ruff/mypy 低水位收口并等待远端 CI 终态 | ## 3. 目标工具分层 @@ -314,6 +314,10 @@ action,并使用 MoviePilot 已配置的具体服务实例访问其自身 API - 受影响 Agent/Skill/MCP/OpenAPI/音乐/架构回归 247 passed;修复全量发现的工作流管理员门禁、插件分页默认值、模块命名治理、服务工具标签和 Schema 导出清单后,专项回归 40 passed - 完整锁定测试最终通过:7,614 passed、9 skipped;覆盖率按 CI 相同的 8 分片采集并合并,Application 81.88%、Domain 81.01%,通过固定 80% 门禁 - 首次推送 `6d4d7331d` 的远端 Pylint 成功;Architecture Contract Gate 在 complexity v2 发现 `plugin.py` 从 999 行增长到 1007 行,合并三个短 import 后恢复到 999 行,本地两套复杂度门禁及 49 项相关回归均通过 -- 当前活动叶子保持 L9;下一步完成提交前差异审查、提交推送并检查远端 CI 终态 +- 第二次推送 `28298c485` 的远端 Pylint 成功;Architecture Contract Gate 识别出两处 Ruff 导入排序债务下降尚未固化,以及新增 service tool/Agent 投影端点未显式处理 mypy 既有基础设施告警 +- 已对新增 Pydantic/ResponseAPIRouter 边界使用精确错误码抑制,补齐 service tool 的 ClassVar 与可选 selector 类型;未通过覆盖基线隐藏任何新增 mypy 错误 +- Ruff 低水位由 576 降至 574,mypy 低水位由 9,606 降至 9,605;mypy、复杂度 v1/v2、并发、异步阻塞、TaskRegistry owner、服务定位和启动性能全部本地通过 +- API/Skill/MCP/provider 专项 86 passed,架构与质量门禁专项 127 passed;生成器重建后工作区合同无额外差异 +- 当前活动叶子保持 L9;下一步提交本轮低水位修复,等待所有远端工作流终态成功后更新为 COMPLETE 本文件作为本次重构的持续记录,保留阶段状态、实际变更、验证结果、提交状态与已知基线边界。 diff --git a/tests/fixtures/architecture/mypy-baseline.json b/tests/fixtures/architecture/mypy-baseline.json index da72bba9f..28eb5eafc 100644 --- a/tests/fixtures/architecture/mypy-baseline.json +++ b/tests/fixtures/architecture/mypy-baseline.json @@ -519,7 +519,7 @@ "no-any-return": 5, "no-untyped-call": 1, "no-untyped-def": 2, - "type-arg": 11 + "type-arg": 10 }, "app/api/endpoints/recommend.py": { "attr-defined": 2, diff --git a/tests/fixtures/architecture/ruff-baseline.json b/tests/fixtures/architecture/ruff-baseline.json index 76781ac45..8f126a76e 100644 --- a/tests/fixtures/architecture/ruff-baseline.json +++ b/tests/fixtures/architecture/ruff-baseline.json @@ -579,9 +579,6 @@ "app/schemas/response.py": { "I001": 1 }, - "app/schemas/site.py": { - "I001": 1 - }, "app/schemas/subscribe.py": { "I001": 1 }, @@ -996,9 +993,6 @@ "tests/test_site_adapter_collector.py": { "I001": 1 }, - "tests/test_skill_scripts_security.py": { - "I001": 1 - }, "tests/test_skills_command.py": { "E402": 6, "I001": 1