chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,508 @@
---
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)