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() は Agent サービスの準備が整っていることを確認するため、MagiService.start() の後に呼び出す必要があります。
Channel ソースマッピング
Bridge は Channel プラグインタイプを MagiMessageSource にマッピングします:
| Channel Plugin Type | MagiMessageSource |
|---|---|
discord | discord |
telegram | telegram |
slack | slack |
qqbot | qqbot |
whatsapp | whatsapp |
email | email |
wechat | wechat |
signal | signal |
line | line |
| その他/未マッピング | api(フォールバック) |
MagiMessageSource は Agent の動作とコンテキストに影響します。ソースが異なると、異なるプロンプト戦略がトリガーされる場合があります。
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 は onIntermediateMessage コールバックを通じてリアルタイムで Channel に転送します:
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 プラグインをゼロから作成する