Files
beaver_project/app-instance/backend/beaver/interfaces/channels/manager.py
steven_li 5ba5c7e4c1 feat(app-instance): 集成Beaver后端并更新配置管理
集成新的Beaver后端服务到应用实例中,替换原有的nanobot实现。

主要变更包括:
- 在Dockerfile和环境配置中添加Beaver相关路径和配置变量
- 更新工作目录结构从.nanobot到.beaver
- 实现Beaver引擎加载器,支持配置文件加载和工具组装
- 添加内置工具如ListDirectoryTool、ReadFileTool、SearchFilesTool
- 更新消息处理流程,支持通道适配器和网关模式
- 重构技能系统,支持显式工具提示和嵌入式检索
- 改进错误处理和生命周期管理

此变更使应用实例能够使用统一的Beaver后端进行AI代理运行时管理。
2026-04-27 17:37:40 +08:00

77 lines
2.7 KiB
Python

"""Channel manager for routing gateway outbound messages."""
from __future__ import annotations
import asyncio
from contextlib import suppress
from beaver.foundation.events import MessageBus, OutboundMessage
from .base import ChannelAdapter
class ChannelManager:
"""Start/stop channel adapters and dispatch outbound messages to them."""
def __init__(self, bus: MessageBus) -> None:
self.bus = bus
self.channels: dict[str, ChannelAdapter] = {}
self.undeliverable: list[OutboundMessage] = []
self.started = False
def register(self, channel: ChannelAdapter) -> None:
if self.started:
raise RuntimeError("Cannot register channels after ChannelManager.start()")
if channel.name in self.channels:
raise ValueError(f"Channel already registered: {channel.name}")
if channel.bus is not self.bus:
raise ValueError("Channel must share the same MessageBus as ChannelManager")
self.channels[channel.name] = channel
async def start(self) -> None:
started: list[ChannelAdapter] = []
try:
for channel in self.channels.values():
await channel.start()
started.append(channel)
except BaseException:
for channel in reversed(started):
with suppress(BaseException):
await channel.stop()
raise
else:
self.started = True
async def stop(self) -> None:
errors: list[BaseException] = []
for channel in reversed(tuple(self.channels.values())):
try:
await channel.stop()
except Exception as exc: # pragma: no cover - defensive cleanup path
errors.append(exc)
self.started = False
if errors:
raise RuntimeError(f"Failed to stop {len(errors)} channel(s)") from errors[0]
async def dispatch_outbound(self, stop_event: asyncio.Event) -> None:
"""Route bus outbound messages until stopped and the queue is drained."""
while True:
if stop_event.is_set() and self.bus.outbound_size == 0:
break
try:
message = await asyncio.wait_for(self.bus.consume_outbound(), timeout=0.25)
except asyncio.TimeoutError:
continue
channel = self.channels.get(message.channel)
if channel is None:
self.undeliverable.append(message)
continue
try:
await channel.send(message)
except Exception: # pragma: no cover - defensive channel isolation
self.undeliverable.append(message)