mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-29 19:37:54 +08:00
严格检查全部宿主源码,清零类型错误预算并移除按模块类型豁免。 按原因、所属方法、错误码及语法指纹锁定必要兼容例外和站点类型桩, CI 阻断新增忽略、检查范围退化、本地桩替代真实接口及新的类型错误。 修复空值、异步和供应方类型边界,保持插件与 API 兼容合同, 并补充存储、订阅、通知、Agent 和工作流边界的回归覆盖。
176 lines
5.0 KiB
Python
176 lines
5.0 KiB
Python
"""
|
||
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",
|
||
)
|