ChannelMagiBridge
ChannelMagiBridge — это мост между системой Channel и слоем обработки Agent, отвечающий за преобразование сообщений Channel в формат MagiIncomingMessage, передачу их в MagiService для обработки и маршрутизацию ответов обратно в исходный Channel.
Расположение исходника: packages/desktop/app/main/services/agent-core/magi/ChannelMagiBridge.ts
Поток данных
sequenceDiagram
participant Router as ChannelMessageRouter
participant Registry as ChannelPluginRegistry
participant Bridge as ChannelMagiBridge
participant Magi as MagiService
participant Plugin as Channel Plugin
Registry->>Bridge: emit('routeToAgent', payload)
Note over Bridge: Convert ChannelMessage → MagiIncomingMessage
Bridge->>Magi: handleMessage(incoming)
alt Agent generates intermediate message
Magi->>Bridge: onIntermediateMessage(text)
Bridge->>Router: sendResponse(channelId, chatId, text, type)
Router->>Plugin: sendMessage(chatId, chunk)
end
Magi-->>Bridge: { response, mode }
alt Final reply exists and no intermediate message was sent
Bridge->>Router: sendResponse(channelId, chatId, response, type)
Router->>Plugin: sendMessage(chatId, chunk)
end
Note over Bridge: Skip final reply if intermediate message<br/>was already sent (avoid duplication)
Жизненный цикл
class ChannelMagiBridge {
constructor(
registry: ChannelPluginRegistry,
channelRouter: ChannelMessageRouter,
magiService: MagiService,
logger: LoggerService,
)
/** Start listening to 'routeToAgent' events */
start(): void
/** Stop listening and cleanup */
stop(): void
/** Whether it is currently active */
isActive(): boolean
}
start() следует вызывать после MagiService.start(), чтобы убедиться, что сервис Agent готов.
Сопоставление источников Channel
Bridge сопоставляет типы плагинов Channel с MagiMessageSource:
| Тип плагина Channel | MagiMessageSource |
|---|---|
discord | discord |
telegram | telegram |
slack | slack |
qqbot | qqbot |
whatsapp | whatsapp |
email | email |
wechat | wechat |
signal | signal |
line | line |
| Другое/не сопоставлено | api (fallback) |
MagiMessageSource влияет на поведение и контекст Agent; разные источники могут запускать разные стратегии prompt.
RouteToAgentPayload
Полезная нагрузка события, отправляемая ChannelMessageRouter через registry.emit('routeToAgent', payload):
interface RouteToAgentPayload {
/** Formatted prompt (original text for DMs, XML for groups) */
prompt: string;
/** Original message that triggered the reply */
sourceMessage: ChannelMessage;
/** All relevant messages (buffer + trigger message) */
allMessages: ChannelMessage[];
/** Channel-specific system prompt returned by plugin */
channelSystemPrompt?: string;
/** Whether it is a one-on-one conversation (DM) */
isDirectConversation: boolean;
/** User permission information */
channelUserPermissions?: {
canUseTool: boolean;
requireConfirmation: boolean;
};
}
Создание MagiIncomingMessage
Bridge преобразует RouteToAgentPayload в MagiIncomingMessage:
const incoming: MagiIncomingMessage = {
content: prompt, // Groups: XML format; DMs: original text
source: CHANNEL_SOURCE_MAP[channelType], // Message source mapping
channelId: sourceMessage.channelId, // Channel instance ID
chatId: sourceMessage.chatId, // Chat/channel ID
userId: sourceMessage.senderId, // Platform user ID
messageId: sourceMessage.id, // Platform message ID
timestamp: Date.now(), // Processing timestamp
channelSystemPrompt, // Plugin-specific system prompt
isDirectConversation, // Whether one-on-one
attachments, // Attachment list
channelUserPermissions, // User permissions
onIntermediateMessage, // Intermediate message callback
};
Передача вложений
Bridge собирает все вложения из allMessages и передает их Agent:
const attachments: MagiAttachment[] = [];
for (const msg of payload.allMessages) {
if (msg.attachments) {
for (const att of msg.attachments) {
if (!att.url && !att.localPath) continue; // Skip attachments without source
attachments.push({
type: att.type,
url: att.url,
localPath: att.localPath,
name: att.name || `attachment_${attachments.length + 1}`,
size: att.size,
});
}
}
}
Вложения берутся из всех сообщений в буфере (а не только из сообщения-триггера), чтобы Agent мог видеть изображения и файлы, отправленные в групповых разговорах.
Обработка промежуточных сообщений
Когда Agent создает промежуточный вывод во время обработки (например, пошаговое рассуждение или результаты вызовов инструментов), Bridge в реальном времени пересылает их в Channel через callback onIntermediateMessage:
onIntermediateMessage: async (text: string) => {
intermediatesSent = true;
await this.channelRouter.sendResponse(
sourceMessage.channelId,
sourceMessage.chatId,
text,
sourceMessage.channelType,
);
}
Логика дедупликации: если ответ уже был отправлен через промежуточные сообщения (intermediatesSent === true), Bridge пропускает отправку финального result.response, чтобы избежать дублирования.
Обработка ошибок
Когда обработка сообщения завершается ошибкой, Bridge отправляет обратную связь об ошибке в исходный Channel:
const errorText = `[Elftia Error] ${error.message}`;
await this.channelRouter.sendResponse(
sourceMessage.channelId,
sourceMessage.chatId,
errorText,
sourceMessage.channelType,
);
Если сама отправка обратной связи об ошибке завершается неудачей, только запишите это в лог и не повторяйте попытку.
Следующие шаги
- Маршрутизация сообщений и конвейер безопасности — поток обработки до того, как сообщения достигают Bridge
- Написание плагина Channel — создание пользовательского плагина Channel с нуля