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
}
Agent 서비스가 준비되도록 start()는 MagiService.start() 이후에 호출해야 합니다.
Channel Source 매핑
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 (fallback) |
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 플러그인을 처음부터 작성하기