Перейти к основному содержимому

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:

Тип плагина ChannelMagiMessageSource
discorddiscord
telegramtelegram
slackslack
qqbotqqbot
whatsappwhatsapp
emailemail
wechatwechat
signalsignal
lineline
Другое/не сопоставлено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,
);

Если сама отправка обратной связи об ошибке завершается неудачей, только запишите это в лог и не повторяйте попытку.

Следующие шаги