メインコンテンツまでスキップ

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 TypeMagiMessageSource
discorddiscord
telegramtelegram
slackslack
qqbotqqbot
whatsappwhatsapp
emailemail
wechatwechat
signalsignal
lineline
その他/未マッピングapi(フォールバック)

MagiMessageSource は Agent の動作とコンテキストに影響します。ソースが異なると、異なるプロンプト戦略がトリガーされる場合があります。

RouteToAgentPayload

ChannelMessageRouterregistry.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 は RouteToAgentPayloadMagiIncomingMessage に変換します:

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,
);

エラーフィードバック自体の送信が失敗した場合は、ログに記録するだけで再試行しません。

次のステップ