# How AionUi's Agent EventBus Enables Dual-Path Broadcasting to Desktop and IM Platforms

> Discover how AionUi's Agent EventBus streamlines dual-path broadcasting. Send AI agent messages from a single source to desktop UI and IM platforms like Lark and Telegram effortlessly.

- Repository: [OfficeAI/AionUi](https://github.com/iofficeai/aionui)
- Tags: architecture
- Published: 2026-02-16

---

**The AionUi Agent EventBus (`ChannelEventBus`) is a singleton EventEmitter that decouples AI agent managers from front-ends, broadcasting structured `IAgentMessageEvent` objects to both the Electron desktop UI and external IM platforms (Lark, Telegram, DingTalk) through a single listener in `ChannelMessageService`.**

The [iOfficeAI/AionUi](https://github.com/iOfficeAI/AionUi) repository implements a sophisticated dual-path communication system that allows AI agents to stream responses simultaneously to a local Electron interface and third-party instant messaging platforms. At the heart of this architecture lies the `ChannelEventBus`, a lightweight global event bus that eliminates tight coupling between background LLM processes and UI components.

## Core Architecture of the AionUi Agent EventBus

### The ChannelEventBus Singleton

Located at [`src/channels/agent/ChannelEventBus.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelEventBus.ts), the `ChannelEventBus` extends Node.js `EventEmitter` and exports a singleton instance called `channelEventBus`. This design ensures that all agent managers and UI services reference the same event pipeline.

The constructor explicitly increases the listener limit to prevent warnings in high-throughput scenarios:

```typescript
// ChannelEventBus.ts constructor
constructor() {
  super();
  this.setMaxListeners(100);
}

```

*Source:* `ChannelEventBus` constructor – [ChannelEventBus.ts, lines 44-48](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelEventBus.ts#L44-L48).

### Event Structure and Types

The bus emits typed events using the `IAgentMessageEvent` interface, which encapsulates the conversation ID, message payload, and metadata. This structure ensures type safety across the TypeScript codebase while allowing flexible payload shapes for different LLM providers.

## Broadcasting from AI Agents

### How Agent Managers Emit Events

Each agent manager (e.g., `GeminiAgentManager`, `CodexAgentManager`, `AcpAgentManager`) imports the singleton `channelEventBus` and emits events whenever the underlying LLM returns partial or final responses. This pattern decouples the streaming logic from the consumption layer.

In [`src/process/task/GeminiAgentManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/task/GeminiAgentManager.ts), the manager emits filtered data like this:

```typescript
// Example in GeminiAgentManager.ts
import { channelEventBus } from '@/channels/agent/ChannelEventBus';
…
channelEventBus.emitAgentMessage(this.conversation_id, filteredData);

```

*Source:* `ChannelEventBus.emitAgentMessage` definition – [ChannelEventBus.ts, lines 54-60](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelEventBus.ts#L54-L60).

## Dual-Path Consumption: Desktop UI and IM Platforms

### ChannelMessageService as the Central Router

The `ChannelMessageService` ([`src/channels/agent/ChannelMessageService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelMessageService.ts)) acts as the single consumer of the `ChannelEventBus`. During initialization, it registers a listener that handles all `AGENT_MESSAGE` events, routing them through a message-transform pipeline before dispatching to the appropriate front-end.

```typescript
// ChannelMessageService.initialize()
this.eventCleanup = channelEventBus.onAgentMessage((event) => {
  this.handleAgentMessage(event);
});

```

*Source:* `ChannelMessageService.initialize` – [ChannelMessageService.ts, lines 70-78](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelMessageService.ts#L70-L78).

### Desktop UI Path (Electron/React)

For desktop interactions, React components obtain the singleton service via `getChannelMessageService()`. When calling `sendMessage()`, they register a streaming callback (`onStream`) that receives every transformed message and updates the UI in real-time. This creates a reactive data flow from the LLM through the bus to the React component tree.

### IM Platform Path (Lark, Telegram, DingTalk)

When a conversation originates from an external IM source, the dual-path system activates **"yoloMode"** (auto-approve tool calls). The `ChannelMessageService.sendMessage()` method detects the source by querying the SQLite conversation record:

```typescript
const isFromChannel = dbResult.success &&
  (dbResult.data?.source === 'lark' ||
   dbResult.data?.source === 'telegram' ||
   dbResult.data?.source === 'dingtalk');

task = await WorkerManage.getTaskByIdRollbackBuild(conversationId, {
  yoloMode: isFromChannel,
});

```

*Source:* `ChannelMessageService.sendMessage()` – [ChannelMessageService.ts, lines 62-68](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelMessageService.ts#L62-L68).

The same `channelEventBus` event travels through the service, but the final payload forwards to the corresponding platform's webhook via the agent's internal `WorkerManage` logic, completing the dual-path broadcast without code duplication.

## Lifecycle Management and Performance

### Listener Scaling with setMaxListeners

The `ChannelEventBus` explicitly calls `setMaxListeners(100)` in its constructor to accommodate multiple UI components, background services, and potential IM channel handlers subscribing simultaneously. This prevents Node.js `EventEmitter` memory leak warnings in high-throughput scenarios.

### Clean Shutdown and Memory Management

The `ChannelMessageService` implements a `shutdown()` method that removes the event listener using the cleanup function returned by `onAgentMessage()`, and clears all active streaming states. This prevents memory leaks across both UI and IM paths when the application window closes or services restart.

## Summary

- **The AionUi Agent EventBus (`ChannelEventBus`)** provides a singleton EventEmitter that decouples AI agents from front-end consumers.
- **Dual-path broadcasting** occurs when agents emit `IAgentMessageEvent` objects once, and `ChannelMessageService` routes them to both the Electron/React UI and external IM platforms (Lark, Telegram, DingTalk).
- **YoloMode activation** automatically approves tool calls for IM-originated conversations, enabling autonomous agent operation in chat platforms.
- **Scalable architecture** supports 100+ listeners with explicit memory management through cleanup functions and shutdown protocols.

## Frequently Asked Questions

### What is the AionUi Agent EventBus?

The **AionUi Agent EventBus** is the `ChannelEventBus` class exported as a singleton from [`src/channels/agent/ChannelEventBus.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelEventBus.ts). It extends Node.js `EventEmitter` to provide a type-safe, decoupled communication layer between background AI agent managers and front-end consumers, enabling a single message source to broadcast to multiple destinations simultaneously.

### How does AionUi broadcast messages to both desktop and IM platforms simultaneously?

AionUi achieves dual-path broadcasting through the **ChannelMessageService**, which registers as the sole listener on the `ChannelEventBus`. When an agent emits an `AGENT_MESSAGE` event, the service receives it and routes the payload through two paths: (1) invoking streaming callbacks for the React/Electron UI components, and (2) forwarding transformed messages to IM webhooks (Lark, Telegram, DingTalk) via the `WorkerManage` logic when the conversation originates from those channels.

### What is yoloMode in AionUi's dual-path system?

**YoloMode** is an execution flag set in `ChannelMessageService.sendMessage()` when detecting that a conversation originated from an IM platform (Lark, Telegram, or DingTalk). When `yoloMode: true` is passed to `WorkerManage.getTaskByIdRollbackBuild()`, the system automatically approves tool calls without requiring user confirmation, enabling autonomous agent operation in chat environments where real-time interaction isn't feasible.

### How does AionUi prevent memory leaks in the EventBus system?

AionUi prevents memory leaks through explicit **lifecycle management** in `ChannelMessageService`. The service stores a cleanup function (`eventCleanup`) returned by `channelEventBus.onAgentMessage()` during initialization. When the application shuts down or the service restarts, calling `shutdown()` executes this cleanup to remove the listener, while also clearing all active streaming states via `clearStreamByConversationId()`, ensuring no orphaned references remain in the EventBus or service layer.