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

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 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, 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:

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

Source: ChannelEventBus constructor – ChannelEventBus.ts, lines 44-48.

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, the manager emits filtered data like this:

// 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.

Dual-Path Consumption: Desktop UI and IM Platforms

ChannelMessageService as the Central Router

The ChannelMessageService (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.

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

Source: ChannelMessageService.initializeChannelMessageService.ts, lines 70-78.

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:

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.

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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →