Files
qiming/qimingclaw/crates/agent-electron-client/docs/v2/05-CHANNELS.md

509 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
version: 2.0
last-updated: 2026-03-09
status: design
---
# 05 — Channel 多渠道接入
## 一、现状分析
### V1 现有实现
`IMService` (`renderer/services/integrations/im.ts`)
- 纯前端实现,支持 Discord / Telegram / DingTalk / Feishu
- 配置存储在内存中(`loadConfigs` / `saveConfigs` → 未完整实现)
- 各平台实现均为框架代码TODO 占位)
- 无框架化的消息路由和 Agent 调度
**问题**
1. 在 Renderer 进程中处理 IM 连接,窗口关闭后不可用
2. 各平台实现高度重复,无统一抽象
3. 收到 IM 消息后无法自动调度 Agent 处理
4. 无消息队列和重试机制
---
## 二、ChannelGateway 设计
### 2.1 核心思路
> **关键特色**QimingClaw 配置的域名(如 `https://testagent.xspaceagi.com`)本身已是一个完整的 Agent 服务。Channel 网关的核心职责是将各 IM 平台的消息**转发到该 Agent 服务**,并将 Agent 的响应**回推到 IM 平台**。
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 飞书 │ │ 钉钉 │ │ Telegram │ │ Discord │
│ Bot │ │ Bot │ │ Bot │ │ Bot │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
└───────────────┼───────────────┼───────────────┘
┌─────────────────────────────────────────────────────────┐
│ ChannelGateway (Main Process) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ ChannelRouter (消息路由) │ │
│ │ IM 消息 ──→ 匹配目标 Agent Service ──→ 转发 │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Agent Service│ │ Local Chat │ │ Agent Service│ │
│ │ (Remote URL) │ │ Engine │ │ (另一个域名) │ │
│ │ testagent. │ │ (本地推理) │ │ prodagent. │ │
│ │ xspaceagi.com│ │ │ │ xspaceagi.com│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ ResponseRelay (响应回推) │ │
│ │ Agent 响应 ──→ 格式化 ──→ 推送到 IM 平台 │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ MessageQueue (消息队列) │ │
│ │ 重试 · 去重 · 限流 · 持久化 │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 2.2 两种消息路由模式
| 模式 | 描述 | 使用场景 |
| ---------------- | ---------------------------------- | --------------------------------------------------- |
| **Remote Agent** | 消息转发到配置的 Agent Service URL | 已有完整 Agent 服务(如 `testagent.xspaceagi.com` |
| **Local Agent** | 消息路由到本地 `ChatEngine` | 使用本地模型/Provider 直接处理 |
```typescript
type ChannelRouteTarget =
| {
type: "remote-agent";
/** 完整的 Agent 服务 URL本身已是一个工作的 Agent 后端 */
serviceUrl: string; // e.g. https://testagent.xspaceagi.com
/** 鉴权方式 */
auth?: {
type: "bearer" | "api-key" | "custom-header";
token: string;
headerName?: string; // custom-header 时使用
};
/** Chat 端点路径(默认 /computer/chat */
chatEndpoint?: string;
/** SSE 端点路径(默认 /computer/progress/{session_id} */
progressEndpoint?: string;
}
| {
type: "local-agent";
/** 本地 Provider + Model */
providerId: string;
modelId: string;
/** 可选系统提示 */
systemPrompt?: string;
};
```
---
## 三、Channel 抽象
### 3.1 统一 Channel 接口
```typescript
/** Channel 适配器接口 */
interface ChannelAdapter {
/** 平台标识 */
readonly platform: ChannelPlatform;
/** 初始化连接 */
connect(config: ChannelConfig): Promise<void>;
/** 断开连接 */
disconnect(): Promise<void>;
/** 连接状态 */
isConnected(): boolean;
/** 发送消息到 IM 平台 */
sendMessage(
target: MessageTarget,
content: ChannelMessageContent,
): Promise<SendResult>;
/** 注册消息回调 */
onMessage(handler: (message: IncomingChannelMessage) => void): void;
/** 注册事件回调(连接/断开/错误) */
onEvent(handler: (event: ChannelEvent) => void): void;
}
type ChannelPlatform =
| "feishu"
| "dingtalk"
| "telegram"
| "discord"
| "wechat-work"
| "slack";
interface ChannelConfig {
id: string; // Channel 实例 ID
platform: ChannelPlatform;
enabled: boolean;
name: string; // 显示名称
/** 平台凭据 */
credentials: ChannelCredentials;
/** 消息路由目标 */
routeTarget: ChannelRouteTarget;
/** 行为配置 */
behavior: {
autoReply: boolean; // 是否自动回复
allowedUsers?: string[]; // 用户白名单(空=全部)
allowedGroups?: string[]; // 群组白名单
triggerKeyword?: string; // 触发关键词(如 @bot
replyFormat: "text" | "markdown" | "card"; // 回复格式
maxConcurrent: number; // 最大并发会话
};
}
type ChannelCredentials =
| { platform: "feishu"; appId: string; appSecret: string }
| {
platform: "dingtalk";
appKey: string;
appSecret: string;
robotCode?: string;
}
| { platform: "telegram"; botToken: string }
| { platform: "discord"; botToken: string; applicationId: string }
| { platform: "wechat-work"; corpId: string; agentId: string; secret: string }
| { platform: "slack"; botToken: string; appToken: string };
```
### 3.2 消息模型
```typescript
/** 收到的 IM 消息 */
interface IncomingChannelMessage {
id: string; // 消息 ID
platform: ChannelPlatform;
channelConfigId: string; // Channel 配置 ID
sender: {
id: string;
name: string;
avatar?: string;
};
/** 消息来源 */
source: {
type: "direct" | "group";
groupId?: string;
groupName?: string;
channelId?: string; // Discord/Slack channel
};
/** 消息内容 */
content: {
text: string;
mentions?: string[];
attachments?: { type: string; url: string; name: string }[];
};
/** 是否 @了机器人 */
mentionedBot: boolean;
timestamp: number;
replyToMessageId?: string;
}
/** 回复内容(平台无关) */
interface ChannelMessageContent {
text: string;
markdown?: string; // 如果平台支持
card?: ChannelCardMessage; // 如果平台支持卡片
attachments?: { name: string; data: Buffer; mimeType: string }[];
}
/** 卡片消息(飞书/钉钉) */
interface ChannelCardMessage {
title: string;
sections: {
type: "text" | "code" | "divider" | "action";
content?: string;
language?: string;
buttons?: { text: string; url?: string; value?: string }[];
}[];
}
```
---
## 四、ChannelGateway 服务
```typescript
class ChannelGateway {
private adapters: Map<string, ChannelAdapter> = new Map();
private configs: Map<string, ChannelConfig> = new Map();
private sessionMap: Map<string, string> = new Map(); // IM会话 → Agent会话
// ==================== 配置管理 ====================
/** 添加/更新 Channel 配置 */
upsertChannel(config: ChannelConfig): Promise<void>;
/** 删除 Channel */
deleteChannel(id: string): Promise<void>;
/** 列出所有 Channel */
listChannels(): ChannelConfig[];
// ==================== 连接管理 ====================
/** 连接指定 Channel */
connect(channelId: string): Promise<{ success: boolean; error?: string }>;
/** 断开指定 Channel */
disconnect(channelId: string): Promise<void>;
/** 连接所有启用的 Channel */
connectAll(): Promise<void>;
/** 获取连接状态 */
getStatus(): ChannelStatus[];
// ==================== 消息处理 ====================
/** 处理收到的 IM 消息 */
private handleIncomingMessage(message: IncomingChannelMessage): Promise<void>;
/**
* 路由到 Remote Agent Service
* → POST {serviceUrl}/computer/chat
* → 监听 SSE {serviceUrl}/computer/progress/{session_id}
* → 将 Agent 响应格式化后回推到 IM
*/
private routeToRemoteAgent(
message: IncomingChannelMessage,
target: RemoteAgentTarget,
): Promise<void>;
/**
* 路由到 Local ChatEngine
* → chatEngine.chat(sessionId, input)
* → 将响应格式化后回推到 IM
*/
private routeToLocalAgent(
message: IncomingChannelMessage,
target: LocalAgentTarget,
): Promise<void>;
}
```
### 4.1 Remote Agent 调用流程
```
IM 消息到达
┌─── ChannelGateway ─────────────────────────────┐
│ │
│ 1. 查找/创建 Agent 会话映射 │
│ IM(userId+groupId) → agentSessionId │
│ │
│ 2. POST {serviceUrl}/computer/chat │
│ { │
│ user_id: sender.id, │
│ project_id: channelConfigId, │
│ message: content.text, │
│ session_id: agentSessionId │
│ } │
│ │
│ 3. 建立 SSE 连接 │
│ GET {serviceUrl}/computer/progress/{sid} │
│ │
│ 4. 监听 SSE 事件,组装回复 │
│ - text_delta → 累积文本 │
│ - tool_call → 可选展示工具调用 │
│ - stream_end → 发送最终回复到 IM │
│ │
│ 5. 格式化回复 │
│ - 纯文本 / Markdown / 卡片消息 │
│ - 按平台能力适配 │
└──────────────────────────────────────────────────┘
```
---
## 五、各平台适配器
### 5.1 飞书 (Feishu / Lark)
```typescript
class FeishuAdapter implements ChannelAdapter {
readonly platform = "feishu";
private eventServer: http.Server; // 事件订阅 HTTP 回调
private accessToken: string;
async connect(config: ChannelConfig): Promise<void> {
const cred = config.credentials as FeishuCredentials;
// 1. 获取 tenant_access_token
this.accessToken = await this.getTenantAccessToken(
cred.appId,
cred.appSecret,
);
// 2. 启动事件订阅 HTTP Server接收飞书推送
this.eventServer = http.createServer(this.handleEvent.bind(this));
// 动态分配端口,避免与本地其他服务冲突
const port =
config.callbackPort || (await this.findAvailablePort(9990, 9999));
this.eventServer.listen(port);
// 或使用 WebSocket 长连接模式
}
async sendMessage(
target: MessageTarget,
content: ChannelMessageContent,
): Promise<SendResult> {
// POST https://open.feishu.cn/open-apis/im/v1/messages
// 支持 text / interactive card / markdown
}
}
```
### 5.2 钉钉 (DingTalk)
```typescript
class DingtalkAdapter implements ChannelAdapter {
readonly platform = "dingtalk";
async connect(config: ChannelConfig): Promise<void> {
const cred = config.credentials as DingtalkCredentials;
// Stream 模式推荐SDK 长连接
// 或 HTTP 回调模式
}
async sendMessage(
target: MessageTarget,
content: ChannelMessageContent,
): Promise<SendResult> {
// POST https://oapi.dingtalk.com/robot/send
// 支持 text / markdown / actionCard
}
}
```
### 5.3 Telegram
```typescript
class TelegramAdapter implements ChannelAdapter {
readonly platform = "telegram";
async connect(config: ChannelConfig): Promise<void> {
const cred = config.credentials as TelegramCredentials;
// Long Polling 模式(无需公网 IP
// getUpdates loop
}
async sendMessage(
target: MessageTarget,
content: ChannelMessageContent,
): Promise<SendResult> {
// POST https://api.telegram.org/bot{token}/sendMessage
// 支持 text / markdown / HTML
}
}
```
### 5.4 工厂模式
```typescript
class ChannelAdapterFactory {
static create(platform: ChannelPlatform): ChannelAdapter {
switch (platform) {
case "feishu":
return new FeishuAdapter();
case "dingtalk":
return new DingtalkAdapter();
case "telegram":
return new TelegramAdapter();
case "discord":
return new DiscordAdapter();
case "slack":
return new SlackAdapter();
default:
throw new Error(`Unsupported platform: ${platform}`);
}
}
}
```
---
## 六、IPC 接口设计
```typescript
// Channel CRUD
'channel:list' ChannelConfig[]
'channel:get' ChannelConfig
'channel:upsert' { success: boolean }
'channel:delete' { success: boolean }
// 连接控制
'channel:connect' { success: boolean; error?: string }
'channel:disconnect' { success: boolean }
'channel:connectAll' { success: boolean }
'channel:status' ChannelStatus[]
// 消息日志
'channel:messages' IncomingChannelMessage[] // 最近的 IM 消息日志
// Events (Main → Renderer)
'channel:event' Event: { type: 'connected' | 'disconnected' | 'message' | 'error'; ... }
```
---
## 七、UI 设计要点
```
┌─ Channel 管理 ──────────────────────────────────────────┐
│ │
│ ● 飞书 Bot ✓ 在线 │ 收到 156 条 │ 回复 148 条 │
│ ● 钉钉 Bot ✓ 在线 │ 收到 89 条 │ 回复 89 条 │
│ ○ Telegram ✗ 未配置 │ │
│ │
│ [+ 新增 Channel] [全部连接] [全部断开] │
│ │
│ ── 飞书 Bot 配置 ────────────────────── │
│ App ID: [cli_xxxxx ] │
│ App Secret: [•••••••••••••••• ] │
│ │
│ 消息路由: ● 远程 Agent 服务 │
│ URL: [https://testagent.xspaceagi.com ] │
│ 认证: [Bearer Token ▾] [••••••••••] │
│ ○ 本地 Agent │
│ Provider: [OpenAI ▾] Model: [gpt-4o ▾] │
│ │
│ 行为: │
│ ☑ 自动回复 触发词: [@QimingClaw ] │
│ ☑ 群聊回复 回复格式: [卡片消息 ▾] │
│ 最大并发: [5] │
│ │
│ [测试连接] [保存] │
└──────────────────────────────────────────────────────────┘
```
---
## 相关文档
- [总体架构](./01-ARCHITECTURE.md)
- [会话管理](./04-SESSION-CHAT.md) — Channel 消息创建的会话
- [V1 IMService](../../src/renderer/services/integrations/im.ts)