Files
MoviePilot/app/agent/loader.py
jxxghp 6d4ce83837 fix(types): clear host mypy debt and prevent typing regressions
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。
按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩,
CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。

修复空值、异步和供应方类型边界,保持插件与 API 兼容合同,
并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
2026-10-21 22:03:18 +08:00

176 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Agent 重量级 canonical 对象的轻量首用入口。
"""
from __future__ import annotations
import sys
import threading
from typing import Any
from app.agent.capabilities import (
AGENT_ENTRYPOINT_KIND,
AGENT_MANAGER_CAPABILITY_ID,
AGENT_SERVICE_CAPABILITY_ID,
AGENT_SERVICE_KIND,
MOVIEPILOT_AGENT_TYPE_CAPABILITY_ID,
TOOL_FACTORY_CAPABILITY_ID,
)
from app.agent.capabilities.adapter import (
AgentEntrypointAdapter,
AgentServiceAdapter,
build_agent_capability_registry,
should_run_agent_service,
)
from app.runtime.capabilities.model import CapabilityMaterializationState
from app.runtime.capabilities.runtime import CapabilityRuntime
_runtime_lock = threading.RLock()
_agent_runtime: CapabilityRuntime | None = None
def _build_agent_runtime() -> CapabilityRuntime:
"""
装配 Agent Runtime;构建阶段只解析 manifests。
"""
return CapabilityRuntime(
build_agent_capability_registry(),
adapters={
AGENT_ENTRYPOINT_KIND: AgentEntrypointAdapter(),
AGENT_SERVICE_KIND: AgentServiceAdapter(),
},
)
def _ensure_runtime() -> CapabilityRuntime:
"""
返回进程唯一 Runtime,同进程关闭后不重新创建。
"""
global _agent_runtime
with _runtime_lock:
if _agent_runtime is None:
_agent_runtime = _build_agent_runtime()
return _agent_runtime
def _materialize_entrypoint(capability_id: str) -> Any:
"""
通过通用 Runtime 完成并发 single-flight 物化,不声明资源运行态。
"""
return _ensure_runtime().materialize(
capability_id,
reason="agent_entrypoint_first_use",
)
def get_agent_manager() -> Any:
"""
返回标准 Agent 管理器;关闭门禁生效后拒绝首次初始化。
"""
return _materialize_entrypoint(AGENT_MANAGER_CAPABILITY_ID)
async def reconcile_agent_service(
*,
reason: str,
changed_keys: set[str] | None = None,
retry: bool = False,
) -> Any | None:
"""
按 manifest watch/selector 协调唯一 Agent Service 生命周期。
"""
runtime = _ensure_runtime()
spec = runtime.get_spec(AGENT_SERVICE_CAPABILITY_ID)
if spec is None:
raise RuntimeError("缺少 agent.service capability")
if changed_keys is not None and not changed_keys.intersection(spec.watch):
return runtime.get_running(AGENT_SERVICE_CAPABILITY_ID)
if not should_run_agent_service(spec):
# stop_async 会等待并发首启后再撤销实例;未物化能力则保持零导入。
await runtime.stop_async(
AGENT_SERVICE_CAPABILITY_ID,
reason=reason,
)
return None
return await runtime.activate_async(
AGENT_SERVICE_CAPABILITY_ID,
reason=reason,
retry=retry,
)
async def activate_agent_service(*, retry: bool = False) -> Any | None:
"""
执行启动期协调;selector 未启用时保持 service 未物化。
"""
return await reconcile_agent_service(
reason="agent_service_startup_reconcile",
retry=retry,
)
def get_running_agent_manager() -> Any | None:
"""
只读返回处于 RUNNING 状态的 Agent 服务;未构建运行时时不触发声明发现。
"""
with _runtime_lock:
runtime = _agent_runtime
if runtime is None:
return None
return runtime.get_running(AGENT_SERVICE_CAPABILITY_ID)
def get_moviepilot_agent_type() -> type:
"""
返回 canonical MoviePilotAgent 类型。
"""
agent_type = _materialize_entrypoint(MOVIEPILOT_AGENT_TYPE_CAPABILITY_ID)
if not isinstance(agent_type, type):
raise TypeError("MoviePilot Agent entrypoint 必须是类型")
return agent_type
def get_tool_factory() -> Any:
"""
返回 canonical 工具工厂类型。
"""
factory_type = _materialize_entrypoint(TOOL_FACTORY_CAPABILITY_ID)
if not isinstance(factory_type, type):
raise TypeError("Agent Tool Factory entrypoint 必须是类型")
return factory_type
def is_tool_factory_materialized() -> bool:
"""
只读判断工具工厂是否已解析;未建 Runtime 时不触发发现或导入。
"""
with _runtime_lock:
runtime = _agent_runtime
if runtime is None:
return False
return (
runtime.snapshot(TOOL_FACTORY_CAPABILITY_ID).materialization
is CapabilityMaterializationState.RESOLVED
)
async def close_materialized_terminal_sessions() -> None:
"""
关闭已物化的终端会话管理器,不触发新的 Agent 工具导入。
"""
module = sys.modules.get("app.agent.terminal.manager")
manager = getattr(module, "terminal_session_manager", None) if module else None
close = getattr(manager, "close", None)
if callable(close):
await close()
async def begin_agent_shutdown() -> bool:
"""
不可逆关闭首用闸门,并返回全部 Agent 能力是否真实收敛。
"""
runtime = _ensure_runtime()
return await runtime.shutdown_async(
reason="application_shutdown",
)