Merge origin/v3 into codex/feat/plugin-data-query-sdk-v3

This commit is contained in:
jxxghp
2026-08-28 06:57:20 +08:00
45 changed files with 1258 additions and 494 deletions
-13
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
from collections.abc import Callable
from typing import Any
AgentDataFactory = Callable[[], Any]
@@ -76,12 +75,6 @@ class DownloadHistoryPort(_PortProxy):
port_name = "download_history"
class WorkflowPort(_PortProxy):
"""工作流数据端口代理。"""
port_name = "workflow"
class PluginDataPort(_PortProxy):
"""插件数据端口代理。"""
@@ -110,7 +103,6 @@ def configure_agent_data_ports(**factories: AgentDataFactory) -> None:
"subscribe_history",
"transfer_history",
"download_history",
"workflow",
"plugin_data",
}
missing = sorted(required - factories.keys())
@@ -167,11 +159,6 @@ def get_agent_download_history_port() -> Any:
return get_agent_data_ports().download_history()
def get_agent_workflow_port() -> Any:
"""创建 Agent 工作流数据端口实例。"""
return get_agent_data_ports().workflow()
def get_agent_plugin_data_port() -> Any:
"""创建 Agent 插件数据端口实例。"""
return get_agent_data_ports().plugin_data()
-2
View File
@@ -6,7 +6,6 @@ from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any, Optional
from app.application.chain.data import ChainDataPorts
from app.application.chain.events import ChainDurableEventWriter
from app.application.configuration import ChainRuntimeConfig
from app.runtime.stop import StopState, runtime_stop_state
@@ -31,7 +30,6 @@ class ChainRuntimeContext:
message_queue_factory: MessageQueueFactory
module_dispatcher_factory: ModuleDispatcherFactory
legacy_transfer_command: Optional[LegacyTransferCommand] = None
data_ports: Optional[ChainDataPorts] = None
durable_event_writer: Optional[ChainDurableEventWriter] = None
configuration: ChainRuntimeConfig = field(
default_factory=lambda: ChainRuntimeConfig(media_extensions=())
+24 -90
View File
@@ -24,7 +24,6 @@ class ChainDataPorts:
site: OperFactory
subscribe: OperFactory
workflow: OperFactory
download_history: OperFactory
transfer_history: OperFactory
transfer_pending: TransferAdmissionRepositoryFactory
@@ -34,94 +33,34 @@ class ChainDataPorts:
user: OperFactory
class _PortProxyMeta(type):
"""让迁移期的 Oper 名称支持按方法打桩,同时仍转发到组合根端口。"""
def __getattr__(cls, name: str) -> Any:
"""把类级方法访问转发到一个新的端口实例。"""
return getattr(cls(), name)
class _ChainDataPortProxy(metaclass=_PortProxyMeta):
"""将旧的 Oper 调用形态转发到 Chain 数据端口的内部代理。"""
port_name: str
def __getattr__(self, name: str) -> Any:
"""转发未被测试替换的数据操作。"""
return getattr(getattr(get_chain_data_ports(), self.port_name)(), name)
class SitePortProxy(_ChainDataPortProxy):
"""站点数据端口代理。"""
port_name = "site"
class SubscribePortProxy(_ChainDataPortProxy):
"""订阅数据端口代理。"""
port_name = "subscribe"
class WorkflowPortProxy(_ChainDataPortProxy):
"""工作流数据端口代理。"""
port_name = "workflow"
class DownloadHistoryPortProxy(_ChainDataPortProxy):
"""下载历史数据端口代理。"""
port_name = "download_history"
class TransferHistoryPortProxy(_ChainDataPortProxy):
"""整理历史数据端口代理。"""
port_name = "transfer_history"
class MediaServerPortProxy(_ChainDataPortProxy):
"""媒体服务器数据端口代理。"""
port_name = "media_server"
class DownloadFailurePortProxy(_ChainDataPortProxy):
"""下载失败数据端口代理。"""
port_name = "download_failure"
class UserPortProxy(_ChainDataPortProxy):
"""用户数据端口代理。"""
port_name = "user"
_ports: Optional[ChainDataPorts] = None
def configure_chain_data_ports(**factories: OperFactory) -> None:
"""由启动组合根登记 Chain 的数据端口实现。"""
required = {
"site",
"subscribe",
"workflow",
"download_history",
"transfer_history",
"transfer_pending",
"transfer_execution",
"media_server",
"download_failure",
"user",
}
missing = sorted(required - factories.keys())
if missing:
raise ValueError(f"Chain 数据端口缺少实现: {', '.join(missing)}")
def configure_chain_data_ports(
*,
site: OperFactory,
subscribe: OperFactory,
download_history: OperFactory,
transfer_history: OperFactory,
transfer_pending: TransferAdmissionRepositoryFactory,
transfer_execution: TransferExecutionRepositoryFactory,
media_server: OperFactory,
download_failure: OperFactory,
user: OperFactory,
) -> None:
"""由启动组合根登记显式命名的 Chain 数据端口实现。"""
global _ports
_ports = ChainDataPorts(**{name: factories[name] for name in required})
_ports = ChainDataPorts(
site=site,
subscribe=subscribe,
download_history=download_history,
transfer_history=transfer_history,
transfer_pending=transfer_pending,
transfer_execution=transfer_execution,
media_server=media_server,
download_failure=download_failure,
user=user,
)
def get_chain_data_ports() -> ChainDataPorts:
@@ -141,11 +80,6 @@ def get_chain_subscribe_port() -> Any:
return get_chain_data_ports().subscribe()
def get_chain_workflow_port() -> Any:
"""创建工作流数据端口实例。"""
return get_chain_data_ports().workflow()
def get_chain_download_history_port() -> Any:
"""创建下载历史数据端口实例。"""
return get_chain_data_ports().download_history()
+15 -5
View File
@@ -4,8 +4,10 @@ from __future__ import annotations
import json
from collections.abc import Awaitable, Callable
from dataclasses import asdict
from typing import Any, Optional
from app.application.workflow import WorkflowSnapshot
from app.schemas.media import resolve_media_identity
@@ -26,8 +28,10 @@ class ServerSharingService:
*,
subscribe_provider: Callable[[int], Any],
async_subscribe_provider: Callable[[int], Awaitable[Any]],
workflow_provider: Callable[[int], Any],
async_workflow_provider: Callable[[int], Awaitable[Any]],
workflow_provider: Callable[[int], Optional[WorkflowSnapshot]],
async_workflow_provider: Callable[
[int], Awaitable[Optional[WorkflowSnapshot]]
],
user_uuid_provider: Callable[[], str],
subscribe_sender: Callable[[dict], Any],
async_subscribe_sender: Callable[[dict], Awaitable[Any]],
@@ -68,9 +72,9 @@ class ServerSharingService:
return payload
@staticmethod
def prepare_workflow(workflow: Any) -> dict:
def prepare_workflow(workflow: WorkflowSnapshot) -> dict:
"""移除本地字段并把动作和流程编码为中心服务兼容格式。"""
workflow_dict = workflow.to_dict()
workflow_dict = asdict(workflow)
workflow_dict.pop("id", None)
workflow_dict.pop("context", None)
workflow_dict["actions"] = json.dumps(workflow_dict["actions"] or [])
@@ -78,7 +82,9 @@ class ServerSharingService:
return workflow_dict
@staticmethod
def validate_workflow(workflow: Any) -> tuple[bool, str]:
def validate_workflow(
workflow: Optional[WorkflowSnapshot],
) -> tuple[bool, str]:
"""验证工作流存在且同时包含动作与流程。"""
if not workflow:
return False, "工作流不存在"
@@ -160,6 +166,8 @@ class ServerSharingService:
valid, message = self.validate_workflow(workflow)
if not valid:
return False, message
if workflow is None:
return False, "工作流不存在"
payload = {
"share_title": share_title,
"share_comment": share_comment,
@@ -188,6 +196,8 @@ class ServerSharingService:
valid, message = self.validate_workflow(workflow)
if not valid:
return False, message
if workflow is None:
return False, "工作流不存在"
payload = {
"share_title": share_title,
"share_comment": share_comment,
+122 -15
View File
@@ -1,11 +1,12 @@
"""工作流状态与定义写操作应用用例。"""
from dataclasses import dataclass
import json
from collections.abc import Awaitable
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Callable, Mapping, Optional, Protocol, TypeVar
from typing import Any, Callable, List, Mapping, Optional, Protocol, TypeVar
from app.schemas.common import JsonData
WORKFLOW_TRIGGER_TIMER = "timer"
WORKFLOW_TRIGGER_EVENT = "event"
@@ -17,6 +18,30 @@ SUPPORTED_WORKFLOW_TRIGGERS = {
}
@dataclass(frozen=True, slots=True)
class WorkflowSnapshot:
"""工作流查询返回的脱离数据库会话的冻结快照。"""
id: int
name: str
description: Optional[str]
timer: Optional[str]
trigger_type: Optional[str]
event_type: Optional[str]
event_conditions: Mapping[str, JsonData]
state: str
current_action: Optional[str]
result: Optional[str]
run_count: Optional[int]
actions: tuple[Mapping[str, JsonData], ...]
flows: tuple[Mapping[str, JsonData], ...]
context: Mapping[str, JsonData]
execution_config: Mapping[str, JsonData]
execution_state: Mapping[str, JsonData]
add_time: Optional[str]
last_time: Optional[str]
class WorkflowRuntime(Protocol):
"""声明宿主入口与 Chain 消费的工作流运行时能力。"""
@@ -40,7 +65,7 @@ class WorkflowRuntime(Protocol):
"""移除全部或指定工作流的事件触发器。"""
...
def update_workflow_event(self, workflow: Any) -> None:
def update_workflow_event(self, workflow: WorkflowSnapshot) -> None:
"""按最新定义刷新工作流事件触发器。"""
...
@@ -87,15 +112,31 @@ def get_workflow_manager() -> WorkflowRuntime:
return _workflow_runtime_provider()
class AsyncWorkflowQueryRepository(Protocol):
"""工作流查询用例需要的异步读取端口。"""
class WorkflowQueryRepository(Protocol):
"""工作流查询用例需要的同步与异步快照端口。"""
async def async_list(self) -> list[Any]:
"""读取全部工作流。"""
def get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
"""按 ID 读取工作流快照"""
...
async def async_get(self, workflow_id: int) -> Optional[Any]:
"""按 ID 读取工作流。"""
def list_enabled(self) -> List[WorkflowSnapshot]:
"""读取全部启用的工作流快照"""
...
def list_timer_enabled(self) -> List[WorkflowSnapshot]:
"""读取启用的定时工作流快照。"""
...
def list_event_enabled(self) -> List[WorkflowSnapshot]:
"""读取启用的事件工作流快照。"""
...
async def async_list(self) -> List[WorkflowSnapshot]:
"""异步读取全部工作流快照。"""
...
async def async_get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
"""异步按 ID 读取工作流快照。"""
...
@@ -114,18 +155,34 @@ class WorkflowCachePort(Protocol):
class WorkflowQueryService:
"""提供工作流列表和详情查询,隔离 API 与数据库会话。"""
def __init__(self, repository: AsyncWorkflowQueryRepository) -> None:
"""保存请求级异步查询端口。"""
def __init__(self, repository: WorkflowQueryRepository) -> None:
"""保存可返回脱离会话快照的查询端口。"""
self._repository = repository
async def list(self) -> list[Any]:
"""返回全部工作流。"""
async def list(self) -> List[WorkflowSnapshot]:
"""返回全部工作流快照"""
return await self._repository.async_list()
async def get(self, workflow_id: int) -> Optional[Any]:
"""返回指定工作流。"""
async def get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
"""返回指定工作流快照"""
return await self._repository.async_get(workflow_id)
def get_sync(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
"""同步返回指定工作流快照。"""
return self._repository.get(workflow_id)
def list_enabled(self) -> List[WorkflowSnapshot]:
"""同步返回全部启用的工作流快照。"""
return self._repository.list_enabled()
def list_timer_enabled(self) -> List[WorkflowSnapshot]:
"""同步返回启用的定时工作流快照。"""
return self._repository.list_timer_enabled()
def list_event_enabled(self) -> List[WorkflowSnapshot]:
"""同步返回启用的事件工作流快照。"""
return self._repository.list_event_enabled()
_configured_workflow_query: WorkflowQueryService | None = None
@@ -183,6 +240,56 @@ class UnitOfWork(Protocol):
...
class WorkflowExecutionPort(Protocol):
"""工作流 Chain 提交执行状态所需的类型化事务端口。"""
def start(self, workflow_id: int) -> bool:
"""提交工作流运行中状态。"""
...
def success(
self,
workflow_id: int,
result: Optional[str] = None,
) -> bool:
"""提交工作流成功状态。"""
...
def fail(self, workflow_id: int, result: str) -> bool:
"""提交工作流失败状态。"""
...
def step(
self,
workflow_id: int,
action_id: str,
context: dict[str, Any],
execution_state: Optional[dict[str, Any]] = None,
) -> bool:
"""提交工作流动作进度。"""
...
def reset(self, workflow_id: int, reset_count: bool = False) -> bool:
"""提交工作流执行状态重置。"""
...
_configured_workflow_execution: Optional[WorkflowExecutionPort] = None
def configure_workflow_execution(service: WorkflowExecutionPort) -> None:
"""由启动组合根登记唯一工作流执行状态事务服务。"""
global _configured_workflow_execution
_configured_workflow_execution = service
def get_configured_workflow_execution() -> WorkflowExecutionPort:
"""返回启动阶段登记的工作流执行状态事务服务。"""
if _configured_workflow_execution is None:
raise RuntimeError("工作流执行状态事务服务尚未配置")
return _configured_workflow_execution
class WorkflowExecutionRepository(Protocol):
"""工作流执行状态写入所需的最小暂存端口。"""