commit 4c395b06e426445e149d788b301d3a760265153e Author: wehub-resource-sync Date: Mon Jul 13 13:20:22 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.aionui/FEATURE_CHANNELS.md b/.aionui/FEATURE_CHANNELS.md new file mode 100644 index 0000000..3f15a42 --- /dev/null +++ b/.aionui/FEATURE_CHANNELS.md @@ -0,0 +1,1035 @@ +# AionUi 个人助手功能开发方案 + +> 本文档记录个人助手功能的完整开发方案,包括架构设计、插件系统、交互设计等。 + +--- + +## 1. 功能概述 + +### 1.1 基本信息 + +- **功能名称**: 个人助手功能 +- **所属模块**: Agent 层、对话系统 +- **涉及进程**: 主进程 (process)、Worker +- **运行环境**: GUI 模式(AionUi 运行中) + +### 1.2 功能描述 + +1. 与 WebUI 功能类似,用户可通过个人终端直接使用 Aion 功能 +2. 主要涉及个人用户的 IM 通信工具(Telegram、Lark/Feishu 等) +3. 打造 7×24 小时个人终端助手 +4. **已实现平台**: Telegram(grammY)、Lark/Feishu(官方 SDK) +5. **支持的 Agent**: Gemini、ACP、Codex + +### 1.3 用户场景 + +``` +触发: 用户通过手机 IM 工具(如 Telegram)发送消息 +过程: 平台机器人接收消息 → 转发给 Aion Agent → LLM 处理 +结果: 处理完成后通过相同平台推送结果给用户 +``` + +### 1.4 参考项目 + +- **Clawdbot**: https://github.com/clawdbot/clawdbot +- 采纳其插件化设计、配对安全模式、Channel 抽象等设计理念 + +--- + +## 2. 整体架构 + +### 2.1 架构概览 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ChannelManager (单例) │ +│ (统一管理所有组件) │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │PluginManager│ │SessionManager│ │PairingService│ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────────────────────┐ │ +│ │ActionExecutor│ │ChannelMessageService │ │ +│ └─────────────┘ └─────────────────────────────┘ │ +└────────────────────┼────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Layer 1: Plugin │ +│ (平台适配层) │ +├─────────────────────────────────────────────────────────────┤ +│ ┌──────────┐ ┌──────────┐ │ +│ │ Telegram │ │ Lark │ ... (Slack, Discord 待实现) │ +│ │ Plugin │ │ Plugin │ │ +│ └────┬─────┘ └────┬─────┘ │ +│ └────────────┴────────────┘ │ +│ │ │ +│ 职责: 接收平台消息/回调 → 转换为统一格式 → 发送响应 │ +│ 不关心: Agent 类型、业务逻辑 │ +└────────────────────┼────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Layer 2: Gateway │ +│ (业务逻辑层) │ +├─────────────────────────────────────────────────────────────┤ +│ ActionExecutor: 系统 Action 处理、对话路由 │ +│ SessionManager: 会话管理、用户授权 │ +│ PairingService: 配对码生成和验证 │ +│ ChannelMessageService: 消息流式处理 │ +│ │ +│ 职责: 系统 Action 处理、对话路由、会话管理、权限控制 │ +│ 不关心: 平台细节、Agent 实现细节 │ +└────────────────────┼────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Layer 3: Agent │ +│ (AI 处理层) │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ Gemini │ │ ACP │ │ Codex │ │ +│ │ Agent │ │ Agent │ │ Agent │ │ +│ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +│ 职责: 与 AI 服务通信、管理对话上下文、返回统一响应 │ +│ 不关心: 消息来源平台、系统级操作 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 数据流 + +``` +入站流程: + 平台消息 → Plugin(转换) → ActionExecutor(路由) → Agent(处理) + + 详细流程: + 1. Plugin 接收平台消息 → toUnifiedIncomingMessage() + 2. PluginManager 调用 messageHandler → ActionExecutor.handleMessage() + 3. ActionExecutor 根据 Action 类型路由: + - Platform Action → Plugin 自行处理 + - System Action → SystemActions 处理 + - Chat Action → ChannelMessageService → Agent + +出站流程: + Agent 响应 → ChannelEventBus → ChannelMessageService → ActionExecutor → Plugin(转换) → 平台发送 + + 详细流程: + 1. Agent Worker 发送消息 → ChannelEventBus.emitAgentMessage() + 2. ChannelMessageService 监听事件 → handleAgentMessage() + 3. transformMessage + composeMessage → StreamCallback + 4. ActionExecutor 调用 context.sendMessage/editMessage() + 5. Plugin 转换消息格式 → sendMessage/editMessage() +``` + +--- + +## 3. 插件系统设计 + +### 3.1 插件职责边界 + +| 插件负责 | 插件不负责 | +| ------------------------------ | ------------------ | +| 连接平台 API | Agent 调度和执行 | +| 接收消息 → 转换为统一格式 | 会话管理和持久化 | +| 统一格式 → 转换为平台消息 | 用户认证和权限控制 | +| 处理平台特有命令 | 消息路由决策 | +| 流式消息更新(编辑已发送消息) | | + +### 3.2 插件生命周期 + +``` +created → initializing → ready → starting → running → stopping → stopped + ↓ ↓ ↓ + error ←←←←←←←←←←←←←←←←←←←←←←←←←←←← +``` + +| 状态 | 说明 | +| -------------- | -------------------- | +| `created` | 插件实例已创建 | +| `initializing` | 正在验证配置和初始化 | +| `ready` | 初始化完成,等待启动 | +| `starting` | 正在连接平台 | +| `running` | 正常运行中 | +| `stopping` | 正在断开连接 | +| `stopped` | 已停止 | +| `error` | 发生错误 | + +### 3.3 插件接口(BasePlugin 抽象类) + +| 接口方法 | 方向 | 说明 | +| ---------------------- | ----------------------- | -------------------------- | +| `initialize(config)` | PluginManager → Plugin | 初始化插件配置 | +| `start()` | PluginManager → Plugin | 启动平台连接 | +| `stop()` | PluginManager → Plugin | 停止平台连接 | +| `sendMessage(...)` | ActionExecutor → Plugin | 发送消息到平台 | +| `editMessage(...)` | ActionExecutor → Plugin | 编辑已发送消息(流式更新) | +| `getStatus()` | PluginManager → Plugin | 获取插件状态 | +| `getActiveUserCount()` | PluginManager → Plugin | 获取活跃用户数 | +| `getBotInfo()` | PluginManager → Plugin | 获取 Bot 信息 | +| `onInitialize()` | 子类实现 | 平台特定的初始化逻辑 | +| `onStart()` | 子类实现 | 平台特定的启动逻辑 | +| `onStop()` | 子类实现 | 平台特定的停止逻辑 | + +### 3.4 统一消息格式 + +**入站消息(平台 → 系统)** - `IUnifiedIncomingMessage` + +| 字段 | 说明 | +| ------------------ | --------------------------------------- | +| `id` | 系统生成的唯一 ID | +| `platform` | 来源平台(telegram/lark/slack/discord) | +| `chatId` | 聊天 ID | +| `user` | 用户信息(id、username、displayName) | +| `content` | 消息内容(type、text、attachments) | +| `timestamp` | 时间戳 | +| `replyToMessageId` | 回复的消息 ID(可选) | +| `action` | Action 信息(按钮回调时) | +| `raw` | 平台原始消息(可选) | + +**出站消息(系统 → 平台)** - `IUnifiedOutgoingMessage` + +| 字段 | 说明 | +| ------------------ | ------------------------------------- | +| `type` | 消息类型(text/image/file/buttons) | +| `text` | 文本内容 | +| `parseMode` | 解析模式(HTML/Markdown/MarkdownV2) | +| `buttons` | Inline 按钮组(可选) | +| `keyboard` | Reply Keyboard(可选) | +| `replyMarkup` | 平台特定 Markup(可选,如 Lark Card) | +| `replyToMessageId` | 回复的消息 ID(可选) | +| `imageUrl` | 图片 URL(image 类型) | +| `fileUrl` | 文件 URL(file 类型) | +| `fileName` | 文件名(file 类型) | +| `silent` | 静默发送(可选) | + +### 3.5 扩展新平台步骤 + +1. 创建 `src/channels/plugins/[platform]/` 目录 +2. 实现 `[Platform]Plugin` 继承 `BasePlugin` +3. 实现 `[Platform]Adapter` 处理消息转换(toUnifiedIncomingMessage, to[Platform]SendParams) +4. 在 `ChannelManager` 构造函数中注册插件:`registerPlugin('platform', PlatformPlugin)` +5. 在 `types.ts` 中添加平台类型到 `PluginType` +6. 添加设置页面 UI +7. 添加 i18n 翻译 +8. 实现平台特定的交互组件(如 Keyboard、Card 等) + +--- + +## 4. 已实现平台 + +### 4.1 Telegram 接入 + +#### 技术选型 + +| 项目 | 选择 | 说明 | +| -------- | ----------------- | ----------------------- | +| Bot 库 | grammY | Clawdbot 使用,API 优雅 | +| 运行模式 | Polling(长轮询) | 自动重连机制 | + +### 4.1 技术选型 + +| 项目 | 选择 | 说明 | +| -------- | -------------------------------- | ----------------------- | +| Bot 库 | grammY | Clawdbot 使用,API 优雅 | +| 运行模式 | Polling(开发)/ Webhook(生产) | 可配置 | + +#### Bot 配置流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Step 1: 创建 Bot │ +│ 用户在 Telegram 中 @BotFather → /newbot → 获取 Token │ +├─────────────────────────────────────────────────────────────┤ +│ Step 2: 配置 Token │ +│ AionUi 设置页面 → 粘贴 Token → 验证 → 保存 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 3: 启动 Bot │ +│ 开启开关 → Bot 开始监听 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 4: 用户配对(见下方安全机制) │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 配置项 + +| 配置项 | 类型 | 说明 | +| ----------- | ----------------- | -------------------------- | +| Bot Token | string | 从 @BotFather 获取 | +| 运行模式 | polling / webhook | Polling 适合开发 | +| Webhook URL | string | 仅 webhook 模式需要 | +| 配对模式 | boolean | 是否需要配对码授权 | +| 速率限制 | number | 每分钟最大消息数 | +| 群组 @提及 | boolean | 群组中是否需要 @bot 才响应 | +| 默认 Agent | gemini | MVP 阶段固定 Gemini | + +#### 配对安全机制(采用 Clawdbot 模式) + +**核心原则**: 批准操作在用户本地设备完成,而非在 Telegram 中完成 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ① 用户在 Telegram 中发起 │ +│ 用户 → @YourBot: /start 或任意消息 │ +├─────────────────────────────────────────────────────────────┤ +│ ② Bot 返回配对请求 │ +│ Bot → 用户: │ +│ "👋 欢迎使用 Aion 助手! │ +│ 您的配对码: ABC123 │ +│ 请在 AionUi 中批准此配对: │ +│ 设置 → Telegram → 待批准请求 → [批准]" │ +├─────────────────────────────────────────────────────────────┤ +│ ③ AionUi 显示待批准请求 │ +│ 设置页面展示: 用户名、配对码、请求时间、[批准]/[拒绝] │ +├─────────────────────────────────────────────────────────────┤ +│ ④ 用户在 AionUi 点击 [批准] │ +├─────────────────────────────────────────────────────────────┤ +│ ⑤ Bot 通知配对成功 │ +│ Bot → 用户: "✅ 配对成功!现在可以开始对话了" │ +└─────────────────────────────────────────────────────────────┘ +``` + +**安全措施** + +| 机制 | 说明 | +| -------------- | ------------------------------------ | +| 配对码认证 | 6位随机码,10分钟有效 | +| 本地批准 | 必须在 AionUi 中批准,非 Telegram 中 | +| 用户白名单 | 仅授权用户可使用 | +| 速率限制 | 防止滥用 | +| Token 加密存储 | 使用 bcrypt 加密 | + +#### 消息转换规则 + +**入站转换(Telegram → 统一格式)** + +| Telegram 消息类型 | 统一消息 content.type | +| ------------------ | -------------------------------- | +| `message:text` | `text` 或 `command`(以 / 开头) | +| `message:photo` | `image` | +| `message:document` | `file` | +| `message:voice` | `audio` | + +**出站转换(统一格式 → Telegram)** + +| 统一消息 type | Telegram API | +| ------------- | --------------------------------- | +| `text` | `sendMessage` | +| `image` | `sendPhoto` | +| `file` | `sendDocument` | +| `buttons` | `sendMessage` + `inline_keyboard` | + +**特殊处理** + +| 场景 | 处理方式 | +| --------- | -------------------------------------------- | +| 流式响应 | 使用 `editMessageText` 更新消息,添加 ▌ 光标 | +| Markdown | 转义特殊字符,使用 `parse_mode: Markdown` | +| @提及移除 | 清理消息中的 `@bot_username` | +| 群组过滤 | 检查是否包含 @提及(可配置) | + +### 4.2 Lark/Feishu 接入 + +#### 技术选型 + +| 项目 | 选择 | 说明 | +| -------- | ------------------------------ | -------------- | +| SDK | @larksuiteoapi/node-sdk | 官方 SDK | +| 运行模式 | WebSocket 长连接 | 无需公网 URL | +| 域 | Feishu(可配置为 Lark 国际版) | 默认使用飞书域 | + +#### Bot 配置流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Step 1: 创建应用 │ +│ 在飞书开放平台创建企业自建应用 → 获取 App ID 和 App Secret │ +├─────────────────────────────────────────────────────────────┤ +│ Step 2: 配置权限 │ +│ 应用权限管理 → 开通"获取与发送单聊、群组消息"权限 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 3: 配置事件订阅 │ +│ 事件订阅 → 订阅"接收消息"事件 → 配置加密密钥(可选) │ +├─────────────────────────────────────────────────────────────┤ +│ Step 4: 配置凭证 │ +│ AionUi 设置页面 → 粘贴 App ID、App Secret → 验证 → 保存 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 5: 启动 Bot │ +│ 开启开关 → Bot 通过 WebSocket 连接开始监听 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 6: 用户配对(见下方安全机制) │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 配置项 + +| 配置项 | 类型 | 说明 | +| ------------------ | ------- | ---------------------- | +| App ID | string | 从飞书开放平台获取 | +| App Secret | string | 从飞书开放平台获取 | +| Encrypt Key | string | 事件加密密钥(可选) | +| Verification Token | string | 事件验证 Token(可选) | +| 配对模式 | boolean | 是否需要配对码授权 | +| 速率限制 | number | 每分钟最大消息数 | +| 默认 Agent | gemini | MVP 阶段固定 Gemini | + +#### 配对安全机制 + +与 Telegram 相同,采用本地批准模式。配对码通过 Lark 消息发送给用户,用户在 AionUi 中批准。 + +#### 消息转换规则 + +**入站转换(Lark → 统一格式)** + +| Lark 消息类型 | 统一消息 content.type | +| --------------- | ---------------------------------- | +| `message:text` | `text` 或 `command`(以 / 开头) | +| `message:image` | `photo` | +| `message:file` | `document` | +| `message:audio` | `audio` | +| Card Action | `action`(通过 extractCardAction) | + +**出站转换(统一格式 → Lark)** + +| 统一消息 type | Lark API | +| ---------------- | -------------------------- | +| `text` | `im.message.create` | +| `buttons` | `im.message.create` + Card | +| Interactive Card | 使用 Lark Card 格式 | + +**特殊处理** + +| 场景 | 处理方式 | +| ---------------- | ------------------------------------------------------ | +| 流式响应 | 使用 `im.message.update` 更新消息 | +| HTML 转 Markdown | convertHtmlToLarkMarkdown() 转换 HTML 为 Lark Markdown | +| Card 交互 | 使用 Lark Card 格式,支持按钮、确认等 | +| 事件去重 | 5 分钟事件缓存,防止重复处理 | + +--- + +## 5. 交互设计 + +### 5.1 设计原则 + +**按钮优先,命令保留**:普通用户通过按钮操作,高级用户可使用命令 + +### 5.2 Telegram 交互组件 + +| 类型 | 说明 | 适用场景 | +| ------------------- | -------------- | ------------------ | +| **Inline Keyboard** | 消息下方的按钮 | 操作确认、选项选择 | +| **Reply Keyboard** | 替换输入法键盘 | 常用操作快捷入口 | +| **Menu Button** | 聊天输入框左侧 | 固定功能入口 | + +### 5.3 交互场景设计 + +**场景 1: 首次使用/配对** + +``` +Bot 消息: +┌─────────────────────────────────────────┐ +│ 👋 欢迎使用 Aion 助手! │ +│ │ +│ 🔑 配对码: ABC123 │ +│ 请在 AionUi 设置中批准此配对 │ +│ │ +│ [📖 使用指南] [❓ 获取帮助] │ +└─────────────────────────────────────────┘ +``` + +**场景 2: 配对成功后(Reply Keyboard 常驻)** + +``` +┌─────────────────────────────────────────┐ +│ ... 对话内容 ... │ +├─────────────────────────────────────────┤ +│ Reply Keyboard (常驻快捷操作) │ +│ [🆕 新对话] [📊 状态] [❓ 帮助] │ +├─────────────────────────────────────────┤ +│ [输入消息...] [发送] │ +└─────────────────────────────────────────┘ +``` + +**场景 3: AI 回复带操作按钮** + +```` +Bot 消息: +┌─────────────────────────────────────────┐ +│ 这是一个快速排序的实现: │ +│ │ +│ ```python │ +│ def quicksort(arr): │ +│ ... │ +│ ``` │ +│ │ +│ [📋 复制] [🔄 重新生成] [💬 继续] │ +└─────────────────────────────────────────┘ +```` + +**场景 4: 设置页面(卡片式选择)** + +``` +Bot 消息: +┌─────────────────────────────────────────┐ +│ ⚙️ 设置 │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 🤖 AI 模型 │ │ +│ │ 当前: Gemini 1.5 Pro │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 💬 对话风格 │ │ +│ │ 当前: 专业 │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ [← 返回] │ +└─────────────────────────────────────────┘ +``` + +### 5.4 按钮与命令对照 + +| 命令(隐藏保留) | 按钮(用户可见) | +| ---------------- | ---------------- | +| `/start` | 自动触发 | +| `/new` | 🆕 新对话 | +| `/status` | 📊 状态 | +| `/help` | ❓ 帮助 | + +--- + +## 6. Action 统一处理机制 + +### 6.1 设计目标 + +命令和按钮回调采用统一处理,避免重复逻辑,便于多平台扩展 + +### 6.2 Action 分类 + +| 类型 | 说明 | 处理方 | +| --------------- | ---------------------------- | --------------------- | +| **平台 Action** | 平台特有操作(认证、配对等) | Plugin 内部处理 | +| **系统 Action** | 平台无关的系统级操作 | Gateway ActionHandler | +| **对话 Action** | 需要 Agent 处理的消息 | AgentRouter → Agent | + +``` +用户输入 + │ + ├─→ 平台 Action → Plugin 自行处理(不进入 Gateway) + │ 例: Telegram 配对、Slack OAuth、Discord 邀请 + │ + ├─→ 系统 Action → Gateway ActionHandler → 统一处理 + │ 例: 会话管理、设置、帮助 + │ + └─→ 对话 Action → AgentRouter → Gemini/ACP/Codex +``` + +### 6.3 系统 Action 列表(平台无关) + +| 分类 | Action | 说明 | +| ------------ | ----------------------- | ------------------ | +| **会话管理** | `session.new` | 创建新会话 | +| | `session.status` | 查看当前状态 | +| | `session.list` | 会话列表(扩展) | +| | `session.switch` | 切换会话(扩展) | +| **设置操作** | `settings.show` | 显示设置菜单 | +| | `settings.model.list` | 显示模型列表 | +| | `settings.model.select` | 选择模型 | +| | `settings.agent.select` | 切换 Agent(扩展) | +| **帮助信息** | `help.show` | 显示帮助 | +| **导航** | `nav.back` | 返回上一级 | +| | `nav.cancel` | 取消当前操作 | + +### 6.4 平台 Action 示例(各 Plugin 自行实现) + +| 平台 | Action | 说明 | +| ------------ | ----------------- | --------------- | +| **Telegram** | `pairing.show` | 显示配对码 | +| | `pairing.refresh` | 刷新配对码 | +| **Slack** | `oauth.start` | 发起 OAuth 授权 | +| | `oauth.callback` | OAuth 回调处理 | +| **Discord** | `invite.generate` | 生成邀请链接 | + +> **注意**: 平台 Action 由各 Plugin 内部处理,不经过 Gateway ActionHandler + +### 6.5 对话 Action 列表 + +| 分类 | Action | 说明 | 路由到 | +| ------------ | ----------------- | -------------- | -------------- | +| **发送消息** | `chat.send` | 用户发送新消息 | 当前会话 Agent | +| **消息操作** | `chat.regenerate` | 重新生成回答 | 当前会话 Agent | +| | `chat.continue` | 继续生成 | 当前会话 Agent | +| | `chat.stop` | 停止生成 | 当前会话 Agent | + +### 6.6 Action 数据结构 + +``` +UnifiedAction { + action: string // Action 类型 + params?: object // 可选参数 + context: { + platform: string // 来源平台 + userId: string // 用户 ID + chatId: string // 聊天 ID + messageId?: string // 触发消息 ID + sessionId?: string // 当前会话 ID + } +} +``` + +### 6.7 按钮回调数据格式 + +``` +格式: action:param1=value1,param2=value2 + +示例: +• "session.new" +• "settings.model.select:id=gemini-pro" +• "chat.regenerate:msg=abc123" +``` + +### 6.8 统一响应格式 + +``` +ActionResponse { + text?: string // 文本内容 + parseMode?: 'plain' | 'markdown' // 解析模式 + buttons?: ActionButton[][] // Inline 按钮 + keyboard?: ActionButton[][] // Reply Keyboard + behavior: 'send' | 'edit' | 'answer' // 响应行为 + toast?: string // Toast 提示 +} +``` + +--- + +## 7. 会话管理 + +### 7.1 会话与 Agent 关系 + +``` +Session { + id: string // 会话 ID + platform: string // 来源平台 + userId: string // 用户 ID + chatId: string // 聊天 ID + + // Agent 配置 + agentType: string // gemini / acp / codex + agentConfig: { + modelId?: string // 模型 ID + } + + // 会话状态 + status: string // active / idle / error + context: object // Agent 会话上下文 + + // 元数据 + createdAt: number + lastActiveAt: number +} +``` + +### 7.2 MVP 阶段会话策略 + +| 项目 | MVP 实现 | +| -------- | ---------------------- | +| 会话模式 | 单活跃会话 | +| 新建会话 | 点击 🆕 按钮清空上下文 | +| 会话存储 | 独立于 AionUi GUI 会话 | +| Agent | 固定 Gemini | +| Model | 使用 AionUi 默认配置 | + +### 7.3 后期扩展 + +| 项目 | 扩展内容 | +| ---------- | -------------------------------------- | +| 多会话 | 支持 `session.list` / `session.switch` | +| Agent 切换 | 支持 `settings.agent.select` | +| Model 切换 | 支持动态选择模型 | +| 会话同步 | Telegram 会话与 AionUi 会话关联 | + +--- + +## 8. 消息流式处理架构 + +### 8.1 架构概览 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Agent Worker (Gemini/ACP/Codex) │ +│ (Agent Worker 进程) │ +├─────────────────────────────────────────────────────────────────┤ +│ 发送消息事件到 IPC Bridge │ +└───────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ChannelEventBus │ +│ (全局事件总线 - 单例) │ +├─────────────────────────────────────────────────────────────────┤ +│ emitAgentMessage(conversationId, data) │ +│ onAgentMessage(handler) → () => void (cleanup) │ +│ │ +│ 事件类型: 'channel.agent.message' │ +│ 数据结构: IAgentMessageEvent { ...IResponseMessage, conv_id } │ +└───────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ChannelMessageService │ +│ (消息服务 - 单例) │ +├─────────────────────────────────────────────────────────────────┤ +│ initialize() { │ +│ // 服务初始化时注册全局事件监听 │ +│ channelEventBus.onAgentMessage(this.handleAgentMessage); │ +│ } │ +│ │ +│ handleAgentMessage(event) { │ +│ // 处理特殊事件: start, finish, error │ +│ // 使用 transformMessage + composeMessage 合并消息 │ +│ // 回调通知: callback(TMessage, isInsert) │ +│ } │ +│ │ +│ sendMessage(sessionId, conversationId, text, callback) { │ +│ // 仅发送消息,不处理监听 │ +│ // 通过 WorkerManage 调用 Agent Task │ +│ } │ +│ │ +│ 内部状态: │ +│ activeStreams: Map │ +│ messageListMap: Map │ +└───────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ActionExecutor │ +│ (业务执行器) │ +├─────────────────────────────────────────────────────────────────┤ +│ handleChatMessage(context, text) { │ +│ messageService.sendMessage( │ +│ sessionId, conversationId, text, │ +│ (message: TMessage, isInsert: boolean) => { │ +│ const outgoing = convertTMessageToOutgoing(message, platform); │ +│ if (isInsert) context.sendMessage(outgoing); │ +│ else context.editMessage(msgId, outgoing); │ +│ } │ +│ ); │ +│ } │ +│ │ +│ convertTMessageToOutgoing(message, platform) { │ +│ // TMessage → IUnifiedOutgoingMessage │ +│ // 根据平台格式化文本(HTML/Markdown) │ +│ // text → 显示内容 │ +│ // tips → 带图标提示 │ +│ // tool_group → 工具状态列表 │ +│ } │ +└───────────────────────────┬─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Plugin (Telegram/Lark) │ +│ (平台插件) │ +├─────────────────────────────────────────────────────────────────┤ +│ sendMessage(chatId, message: IUnifiedOutgoingMessage) │ +│ editMessage(chatId, messageId, message: IUnifiedOutgoingMessage)│ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 8.2 事件类型处理 + +| 事件类型 | 来源 | 处理方式 | +| ------------------- | -------------- | -------------------------------------------- | +| `start` | Agent 开始响应 | 重置消息列表 | +| `content` | 流式文本块 | transformMessage → composeMessage → callback | +| `tool_group` | 工具调用状态 | 合并到现有 tool_group 或新增 | +| `finish`/`finished` | 响应完成 | resolve promise,清理状态 | +| `error` | 发生错误 | reject promise,清理状态 | +| `thought` | 思考过程 | 忽略(transformMessage 返回 undefined) | + +### 8.3 消息合并策略 (composeMessage) + +| 消息类型 | 合并规则 | +| ------------ | ------------------------------------ | +| `text` | 同 msg_id 累加内容,不同 msg_id 新增 | +| `tool_group` | 按 callId 合并工具状态更新 | +| `tool_call` | 按 callId 合并 | +| `tips` | 直接新增 | + +### 8.4 消息回调参数 + +```typescript +type StreamCallback = (chunk: TMessage, isInsert: boolean) => void; + +// isInsert = true: 新消息,调用 sendMessage 发送新消息 +// isInsert = false: 更新消息,调用 editMessage 编辑现有消息 +``` + +### 8.5 节流控制 + +| 参数 | 值 | 说明 | +| ------------------ | ------ | ------------------------- | +| UPDATE_THROTTLE_MS | 500ms | 消息编辑最小间隔 | +| 发送新消息 | 无限制 | isInsert=true 时立即发送 | +| 编辑消息 | 节流 | isInsert=false 时应用节流 | + +- [ ] 使用现有 Context: **\*\*\*\***\_\_\_\_**\*\*\*\*** +- [ ] 需要新增 Context: **\*\*\*\***\_\_\_\_**\*\*\*\*** +- [ ] 仅组件内部状态 (useState/useReducer) +- [ ] 需要持久化存储 + +### 8.6 关键设计原则 + +1. **事件监听与消息发送分离** + - 事件监听在服务初始化时完成(`initialize()`) + - `sendMessage()` 仅负责发送消息,不处理监听 + +2. **全局事件总线解耦** + - `ChannelMessageService` 不直接与 Agent Task 交互 + - 通过 `ChannelEventBus` 全局事件总线解耦 + +3. **统一消息格式** + - 内部使用 `TMessage` 统一消息格式 + - 输出时转换为 `IUnifiedOutgoingMessage` + +--- + +## 9. Agent 接口规范 + +### 8.1 每个 Agent 需实现的能力 + +| 能力 | 说明 | +| --------------- | ------------------ | +| `sendMessage` | 发送消息并获取响应 | +| `streamMessage` | 流式发送消息 | +| `regenerate` | 重新生成上一条回复 | +| `continue` | 继续生成 | +| `stop` | 停止当前生成 | +| `getContext` | 获取会话上下文 | +| `clearContext` | 清空会话上下文 | + +### 8.2 Agent 响应格式 + +``` +AgentResponse { + type: 'text' | 'stream_start' | 'stream_chunk' | 'stream_end' | 'error' + text?: string + chunk?: string + error?: { code: string, message: string } + metadata?: { + model?: string + tokensUsed?: number + duration?: number + } + suggestedActions?: ActionButton[] +} +``` + +--- + +## 9. 文件结构(实际实现) + +``` +src/channels/ +├── core/ # 核心模块 +│ ├── ChannelManager.ts # 统一管理器(单例) +│ └── SessionManager.ts # 会话管理 +│ +├── gateway/ # 网关层 +│ ├── PluginManager.ts # 插件生命周期管理 +│ └── ActionExecutor.ts # Action 执行器(路由、消息处理) +│ +├── actions/ # Action 处理(平台无关) +│ ├── types.ts # Action/Response 类型定义 +│ ├── SystemActions.ts # 系统 Action(会话、设置、帮助) +│ ├── ChatActions.ts # 对话 Action(发送、重新生成等) +│ └── PlatformActions.ts # 平台 Action(配对等) +│ +├── agent/ # Agent 集成 +│ ├── ChannelEventBus.ts # 全局事件总线 +│ └── ChannelMessageService.ts # 消息流式处理服务 +│ +├── pairing/ # 配对服务 +│ └── PairingService.ts # 配对码生成和验证(平台无关) +│ +├── plugins/ # 插件目录 +│ ├── BasePlugin.ts # 插件抽象基类 +│ ├── telegram/ +│ │ ├── TelegramPlugin.ts # Telegram 插件 +│ │ ├── TelegramAdapter.ts # 消息适配器 +│ │ └── TelegramKeyboards.ts # 键盘组件 +│ └── lark/ +│ ├── LarkPlugin.ts # Lark 插件 +│ ├── LarkAdapter.ts # 消息适配器 +│ └── LarkCards.ts # Card 组件 +│ +├── utils/ # 工具函数 +│ └── credentialCrypto.ts # 凭证加密 +│ +└── types.ts # 类型定义 +``` + +--- + +## 10. 数据库设计 + +| 表名 | 用途 | +| ------------------------- | ------------------------- | +| `assistant_plugins` | 插件配置(Token、模式等) | +| `assistant_users` | 已授权用户列表 | +| `assistant_sessions` | 用户会话关联 | +| `assistant_pairing_codes` | 待批准的配对请求 | + +--- + +## 11. 外部依赖 + +| 依赖包 | 用途 | 说明 | +| ------------------------- | --------------------- | ----------------------- | +| `grammy` | Telegram Bot | Clawdbot 使用,API 优雅 | +| `@larksuiteoapi/node-sdk` | Lark/Feishu Bot | 官方 SDK | +| `@slack/bolt` | Slack Bot(待实现) | 官方 SDK | +| `discord.js` | Discord Bot(待实现) | 官方 SDK | + +--- + +## 12. 实现状态 + +### 12.1 已实现功能 + +#### Telegram + +- [x] Bot Token 配置和验证 +- [x] Bot 启动/停止控制(Polling 模式,自动重连) +- [x] 配对码生成和本地批准流程 +- [x] 已授权用户管理 +- [x] 按钮交互(Reply Keyboard + Inline Keyboard) +- [x] 与 Gemini/ACP/Codex Agent 对话 +- [x] 新建会话功能 +- [x] 流式消息响应(editMessage 更新) +- [x] 工具确认交互 +- [x] 错误恢复机制 + +#### Lark/Feishu + +- [x] App ID/Secret 配置和验证 +- [x] Bot 启动/停止控制(WebSocket 长连接) +- [x] 配对码生成和本地批准流程 +- [x] 已授权用户管理 +- [x] Card 交互(按钮、确认等) +- [x] 与 Gemini/ACP/Codex Agent 对话 +- [x] 新建会话功能 +- [x] 流式消息响应(updateMessage 更新) +- [x] 工具确认交互(Card 格式) +- [x] 事件去重机制(5 分钟缓存) +- [x] HTML 转 Lark Markdown + +#### 核心功能 + +- [x] ChannelManager 统一管理 +- [x] PluginManager 插件生命周期管理 +- [x] SessionManager 会话管理 +- [x] PairingService 配对服务 +- [x] ActionExecutor Action 路由和执行 +- [x] ChannelMessageService 消息流式处理 +- [x] ChannelEventBus 全局事件总线 +- [x] 凭证加密存储 +- [x] 多平台统一消息格式 + +### 12.2 安全验收 + +- [x] 配对码 10 分钟过期 +- [x] 必须在 AionUi 本地批准 +- [x] 未授权用户无法使用 +- [x] Token/凭证加密存储 +- [ ] 速率限制(待实现) + +### 12.3 兼容性 + +- [x] macOS 正常运行 +- [x] Windows 正常运行 +- [x] 多语言支持(i18n) + +--- + +## 13. 后期扩展路线 + +| 阶段 | 内容 | 状态 | +| ----------- | --------------------------- | ----------- | +| **Phase 1** | Telegram + Lark 接入 | ✅ 已完成 | +| **Phase 2** | 多会话管理、会话切换 | 🔄 待实现 | +| **Phase 3** | Agent 切换(已支持,需 UI) | 🔄 部分完成 | +| **Phase 4** | Model 动态切换 | 🔄 待实现 | +| **Phase 5** | Slack 平台接入 | 🔄 待实现 | +| **Phase 6** | Discord 平台接入 | 🔄 待实现 | +| **Phase 7** | 速率限制 | 🔄 待实现 | +| **Phase 8** | 会话与 AionUi 同步 | 🔄 待实现 | +| **Phase 9** | Headless 独立服务模式 | 🔄 待实现 | + +--- + +## 模板维护 + +- **创建日期**: 2025-01-27 +- **最后更新**: 2026-02-03 +- **适用版本**: AionUi v1.7.8+ +- **维护者**: 项目团队 + +--- + +## 附录:关键实现细节 + +### A.1 ChannelManager 初始化流程 + +```typescript +1. ChannelManager.getInstance().initialize() + ├─ 初始化 PluginManager + ├─ 初始化 SessionManager + ├─ 初始化 PairingService + ├─ 初始化 ActionExecutor + └─ 初始化 ChannelMessageService + +2. 加载数据库中的插件配置 +3. 为每个启用的插件调用 initialize() 和 start() +``` + +### A.2 消息处理流程 + +```typescript +1. Plugin 接收平台消息 + └─ toUnifiedIncomingMessage() 转换 + +2. PluginManager 调用 messageHandler + └─ ActionExecutor.handleMessage() + +3. ActionExecutor 路由 Action + ├─ Platform Action → PlatformActions + ├─ System Action → SystemActions + └─ Chat Action → ChannelMessageService + +4. ChannelMessageService.sendMessage() + └─ 通过 WorkerManage 调用 Agent Task + +5. Agent 响应 → ChannelEventBus + └─ ChannelMessageService.handleAgentMessage() + └─ StreamCallback → ActionExecutor + └─ Plugin.sendMessage/editMessage() +``` + +### A.3 平台特定实现 + +**Telegram** + +- 使用 grammY 库 +- Polling 模式,支持自动重连 +- Inline Keyboard + Reply Keyboard +- HTML 格式消息 + +**Lark/Feishu** + +- 使用官方 Node SDK +- WebSocket 长连接模式 +- Card 格式交互 +- Lark Markdown 格式消息 +- 事件去重机制(5 分钟缓存) diff --git a/.aionui/FEATURE_CHANNELS_LARK_LARK.md b/.aionui/FEATURE_CHANNELS_LARK_LARK.md new file mode 100644 index 0000000..e3ab1fb --- /dev/null +++ b/.aionui/FEATURE_CHANNELS_LARK_LARK.md @@ -0,0 +1,427 @@ +# 飞书 (Lark) 接入方案 + +> 本文档记录飞书平台接入的完整开发方案,基于现有 Telegram 插件架构进行扩展。 + +--- + +## 1. 功能概述 + +### 1.1 基本信息 + +- **功能名称**: 飞书机器人接入 +- **所属模块**: Channel Plugin 层 +- **涉及进程**: 主进程 (process) +- **运行环境**: GUI 模式(AionUi 运行中) +- **依赖**: 现有 Channel 架构、PairingService、SessionManager + +### 1.2 功能描述 + +1. 复用现有 Channel 插件架构,新增飞书平台支持 +2. 用户可通过飞书机器人与 AionUi 进行对话 +3. 支持 Gemini、Claude、Codex 等多 Agent 切换 +4. 与 Telegram 功能完全对齐 + +### 1.3 用户场景 + +``` +触发: 用户在飞书中 @AionBot 或私聊发送消息 +过程: 飞书机器人接收消息 → 转发给 Aion Agent → LLM 处理 +结果: 处理完成后通过飞书消息卡片推送结果给用户 +``` + +### 1.4 参考资源 + +- **飞书开放平台**: https://open.feishu.cn/ +- **Node SDK**: https://github.com/larksuite/node-sdk +- **现有实现**: `src/channels/plugins/telegram/` + +--- + +## 2. 技术选型 + +### 2.1 平台对比 + +| 项目 | Telegram | 飞书 | +| ------------ | -------------------------------- | -------------------------- | +| **Bot 库** | grammY | @larksuiteoapi/node-sdk | +| **运行模式** | Polling / Webhook | WebSocket 长连接 / Webhook | +| **认证方式** | Bot Token | App ID + App Secret | +| **交互组件** | Inline Keyboard + Reply Keyboard | 消息卡片 (Message Card) | +| **消息格式** | Markdown / HTML | 富文本 / 消息卡片 JSON | +| **流式更新** | editMessageText | PATCH /im/v1/messages/:id | + +### 2.2 技术选择 + +| 项目 | 选择 | 说明 | +| -------- | ----------------------- | -------------------------- | +| SDK | @larksuiteoapi/node-sdk | 官方 Node.js SDK | +| 运行模式 | WebSocket (优先) | 无需公网地址,适合桌面应用 | +| 消息格式 | 消息卡片 | 支持富文本和交互按钮 | + +--- + +## 3. 配置流程 + +### 3.1 飞书应用创建 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Step 1: 创建应用 │ +│ 飞书开放平台 → 创建企业自建应用 → 获取 App ID/Secret │ +├─────────────────────────────────────────────────────────────┤ +│ Step 2: 开启机器人能力 │ +│ 应用能力 → 机器人 → 开启 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 3: 配置权限 │ +│ 权限管理 → 添加以下权限: │ +│ • im:message (获取与发送单聊、群组消息) │ +│ • im:message.group_at_msg (接收群聊@机器人消息) │ +│ • im:chat (获取群组信息) │ +│ • contact:user.id:readonly (获取用户 ID) │ +├─────────────────────────────────────────────────────────────┤ +│ Step 4: 发布应用 │ +│ 版本管理与发布 → 创建版本 → 申请发布 │ +├─────────────────────────────────────────────────────────────┤ +│ Step 5: 配置 AionUi │ +│ 设置 → Channels → 飞书 → 粘贴 App ID/Secret → 启动 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 3.2 配置项 + +| 配置项 | 类型 | 说明 | 必填 | +| ----------- | -------------------- | ------------------- | :--: | +| App ID | string | 飞书应用 ID | ✅ | +| App Secret | string | 飞书应用密钥 | ✅ | +| 运行模式 | websocket / webhook | 事件接收模式 | ✅ | +| Webhook URL | string | 仅 webhook 模式需要 | ❌ | +| 配对模式 | boolean | 是否需要配对码授权 | ✅ | +| 速率限制 | number | 每分钟最大消息数 | ❌ | +| 默认 Agent | gemini / acp / codex | 默认使用的 Agent | ✅ | + +--- + +## 4. 配对安全机制 + +### 4.1 流程设计(与 Telegram 一致) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ① 用户在飞书中发起 │ +│ 用户 → @AionBot: 任意消息 │ +├─────────────────────────────────────────────────────────────┤ +│ ② Bot 返回配对请求(消息卡片) │ +│ ┌────────────────────────────────────────┐ │ +│ │ 👋 欢迎使用 Aion 助手! │ │ +│ │ │ │ +│ │ 🔑 配对码: ABC123 │ │ +│ │ 请在 AionUi 中批准此配对 │ │ +│ │ │ │ +│ │ [📖 使用指南] [🔄 刷新状态] │ │ +│ └────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ ③ AionUi 显示待批准请求 │ +│ 设置页面展示: 用户名、配对码、请求时间、[批准]/[拒绝] │ +├─────────────────────────────────────────────────────────────┤ +│ ④ 用户在 AionUi 点击 [批准] │ +├─────────────────────────────────────────────────────────────┤ +│ ⑤ Bot 推送配对成功消息 │ +│ Bot → 用户: "✅ 配对成功!现在可以开始对话了" │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 4.2 安全措施 + +| 机制 | 说明 | +| ------------ | ------------------------------ | +| 配对码认证 | 6位随机码,10分钟有效 | +| 本地批准 | 必须在 AionUi 中批准,非飞书中 | +| 用户白名单 | 仅授权用户可使用 | +| 速率限制 | 防止滥用 | +| 凭证加密存储 | 使用加密存储 App Secret | + +--- + +## 5. 消息转换规则 + +### 5.1 入站转换(飞书 → 统一格式) + +| 飞书事件类型 | 统一消息 content.type | +| ------------------------------- | --------------------- | +| `im.message.receive_v1` (text) | `text` | +| `im.message.receive_v1` (image) | `image` | +| `im.message.receive_v1` (file) | `file` | +| `im.message.receive_v1` (audio) | `audio` | +| `card.action.trigger` | `action` | + +### 5.2 出站转换(统一格式 → 飞书) + +| 统一消息 type | 飞书 API | content_type | +| ------------- | ------------------------- | ------------ | +| `text` | POST /im/v1/messages | text | +| `image` | POST /im/v1/messages | image | +| `buttons` | POST /im/v1/messages | interactive | +| 流式更新 | PATCH /im/v1/messages/:id | - | + +### 5.3 消息卡片结构 + +```json +{ + "config": { + "wide_screen_mode": true + }, + "header": { + "title": { + "tag": "plain_text", + "content": "Aion 助手" + } + }, + "elements": [ + { + "tag": "markdown", + "content": "消息内容..." + }, + { + "tag": "action", + "actions": [ + { + "tag": "button", + "text": { "tag": "plain_text", "content": "🆕 新对话" }, + "type": "primary", + "value": { "action": "session.new" } + } + ] + } + ] +} +``` + +--- + +## 6. 交互设计 + +### 6.1 组件映射 + +| 场景 | Telegram | 飞书 | +| ---------------- | -------------------- | ------------------ | +| **常驻快捷操作** | Reply Keyboard | 消息卡片底部按钮组 | +| **消息操作按钮** | Inline Keyboard | 消息卡片交互按钮 | +| **配对请求** | 文本 + 按钮 | 消息卡片 | +| **AI 回复** | Markdown + 按钮 | 富文本/卡片 + 按钮 | +| **设置菜单** | 多级 Inline Keyboard | 消息卡片 | + +### 6.2 交互场景 + +**场景 1: 配对成功后的主菜单** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 消息卡片 (Message Card) │ +├─────────────────────────────────────────────────────────────┤ +│ ✅ 配对成功!现在可以开始对话了 │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [🆕 新对话] [🔄 Agent] [📊 状态] [❓ 帮助] │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**场景 2: AI 回复带操作按钮** + +```` +┌─────────────────────────────────────────────────────────────┐ +│ 消息卡片 (Message Card) │ +├─────────────────────────────────────────────────────────────┤ +│ 这是一个快速排序的实现: │ +│ │ +│ ```python │ +│ def quicksort(arr): │ +│ if len(arr) <= 1: │ +│ return arr │ +│ ... │ +│ ``` │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [📋 复制] [🔄 重新生成] [💬 继续] │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +```` + +**场景 3: Agent 切换** + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 消息卡片 (Message Card) │ +├─────────────────────────────────────────────────────────────┤ +│ 🔄 切换 Agent │ +│ │ +│ 选择一个 AI Agent: │ +│ 当前: 🤖 Gemini │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ [✓ 🤖 Gemini] [🧠 Claude] [⚡ Codex] │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 7. 文件结构 + +``` +src/channels/ +├── plugins/ +│ ├── telegram/ # 现有 Telegram 插件 +│ │ ├── TelegramPlugin.ts +│ │ ├── TelegramAdapter.ts +│ │ ├── TelegramKeyboards.ts +│ │ └── index.ts +│ │ +│ └── lark/ # 新增飞书插件 +│ ├── LarkPlugin.ts # 飞书插件主类 +│ ├── LarkAdapter.ts # 消息格式转换 +│ ├── LarkCards.ts # 消息卡片模板 +│ └── index.ts +│ +├── types.ts # 需要新增 'lark' 到 PluginType +└── ... +``` + +--- + +## 8. 接口设计 + +### 8.1 LarkPlugin 类 + +```typescript +class LarkPlugin extends BasePlugin { + // 生命周期 + async initialize(config: LarkPluginConfig): Promise; + async start(): Promise; + async stop(): Promise; + + // 消息处理 + async sendMessage(chatId: string, message: IUnifiedOutgoingMessage): Promise; + async editMessage(chatId: string, messageId: string, message: IUnifiedOutgoingMessage): Promise; + + // 事件处理 + private handleMessageEvent(event: LarkMessageEvent): void; + private handleCardAction(action: LarkCardAction): void; + + // Token 管理 + private async refreshAccessToken(): Promise; +} +``` + +### 8.2 配置接口 + +```typescript +interface LarkPluginConfig { + appId: string; + appSecret: string; + mode: 'websocket' | 'webhook'; + webhookUrl?: string; + encryptKey?: string; // 事件加密密钥 + verificationToken?: string; // 事件验证令牌 +} +``` + +--- + +## 9. 飞书特有注意事项 + +| 项目 | 说明 | +| ---------------- | ------------------------------------------ | +| **应用类型** | 建议使用企业自建应用,个人开发者有功能限制 | +| **权限审核** | 部分权限需要管理员审批 | +| **消息卡片限制** | 卡片 JSON 最大 30KB,需要分片处理长消息 | +| **Token 刷新** | Access Token 有效期 2 小时,需要自动刷新 | +| **事件订阅** | WebSocket 模式无需公网地址,更适合桌面应用 | +| **@提及** | 群聊中需要 @机器人 才会收到消息 | + +--- + +## 10. 开发计划 + +### Phase 1: 基础连接 (预计 2-3 天) + +- [ ] 创建 LarkPlugin 基类 +- [ ] 实现 WebSocket 事件接收 +- [ ] 实现 Access Token 自动刷新 +- [ ] 基础消息收发功能 + +### Phase 2: 安全认证 (预计 1-2 天) + +- [ ] 复用 PairingService +- [ ] 配对流程消息卡片 +- [ ] 设置页面 UI 适配 + +### Phase 3: 交互完善 (预计 2-3 天) + +- [ ] 消息卡片模板系统 +- [ ] 按钮回调处理 +- [ ] Agent 切换功能 +- [ ] 流式响应支持 + +### Phase 4: 优化 (预计 1-2 天) + +- [ ] 长消息分片处理 +- [ ] 错误处理完善 +- [ ] 多语言支持 +- [ ] 日志与监控 + +--- + +## 11. 功能对齐清单 + +| 功能 | Telegram | 飞书 | 复用组件 | +| ------------- | :------: | :--: | --------------------- | +| Bot 配置验证 | ✅ | 🔲 | - | +| Bot 启动/停止 | ✅ | 🔲 | ChannelManager | +| 配对码认证 | ✅ | 🔲 | PairingService | +| 本地批准流程 | ✅ | 🔲 | 现有 UI | +| 用户白名单 | ✅ | 🔲 | Database | +| 按钮交互 | ✅ | 🔲 | SystemActions | +| 流式响应 | ✅ | 🔲 | ChannelMessageService | +| Agent 切换 | ✅ | 🔲 | SystemActions | +| 新建会话 | ✅ | 🔲 | SessionManager | +| 速率限制 | ✅ | 🔲 | RateLimiter | + +--- + +## 12. 验收标准 + +### 12.1 功能验收 + +- [ ] 飞书应用凭证配置和验证 +- [ ] Bot 启动/停止控制 +- [ ] 配对码生成和本地批准流程 +- [ ] 已授权用户管理 +- [ ] 消息卡片交互 +- [ ] 与 Gemini/Claude Agent 对话 +- [ ] Agent 切换功能 +- [ ] 新建会话功能 +- [ ] 流式消息响应 + +### 12.2 安全验收 + +- [ ] 配对码 10 分钟过期 +- [ ] 必须在 AionUi 本地批准 +- [ ] 未授权用户无法使用 +- [ ] App Secret 加密存储 +- [ ] 速率限制生效 + +### 12.3 兼容性 + +- [ ] macOS 正常运行 +- [ ] Windows 正常运行 +- [ ] 多语言支持 + +--- + +## 模板维护 + +- **创建日期**: 2026-01-30 +- **最后更新**: 2026-01-30 +- **适用版本**: AionUi v0.x+ +- **维护者**: 项目团队 diff --git a/.aionui/FEATURE_DEV_TEMPLATE.md b/.aionui/FEATURE_DEV_TEMPLATE.md new file mode 100644 index 0000000..ffa8362 --- /dev/null +++ b/.aionui/FEATURE_DEV_TEMPLATE.md @@ -0,0 +1,366 @@ +# AionUi 功能开发规范模板 + +> 本模板用于规范化向 AI 描述功能开发需求,确保 AI 能够准确理解任务并遵循项目约定。 + +--- + +## 1. 功能概述 + +### 1.1 基本信息 + +- **功能名称**: [简洁命名] +- **所属模块**: [ ] Agent层 [ ] 对话系统 [ ] 预览系统 [ ] 设置系统 [ ] 工作区 [ ] 其他 +- **涉及进程**: [ ] 主进程(process) [ ] 渲染进程(renderer) [ ] WebServer [ ] Worker + +### 1.2 功能描述 + +[用 1-3 句话描述功能的核心目的和价值] + +### 1.3 用户场景 + +``` +触发: [用户如何触发此功能] +过程: [系统如何响应] +结果: [功能完成后的状态] +``` + +### 1.4 数据流 + +| 方向 | 数据类型 | 说明 | +| ---- | -------- | ---- | +| 输入 | | | +| 输出 | | | + +--- + +## 2. 开发规范 + +### 2.1 技术栈约束 + +- **框架**: Electron 37 + React 19 + TypeScript 5.8 +- **UI库**: Arco Design (@arco-design/web-react) +- **图标**: Icon Park (@icon-park/react) +- **CSS**: UnoCSS 原子化样式 +- **状态管理**: React Context (AuthContext / ConversationContext / ThemeContext / LayoutContext) +- **IPC通信**: @office-ai/platform bridge 系统 +- **国际化**: i18next + react-i18next +- **数据库**: better-sqlite3 + +### 2.2 命名规范 + +| 类型 | 规范 | 示例 | +| ------------ | --------------------- | ----------------------------------------------- | +| React 组件 | PascalCase | `MessageList.tsx`, `FilePreview.tsx` | +| Hooks | use 前缀 + PascalCase | `useAutoScroll.ts`, `useColorScheme.ts` | +| Bridge 文件 | 功能名 + Bridge | `conversationBridge.ts`, `databaseBridge.ts` | +| Service 文件 | 功能名 + Service | `WebuiService.ts` | +| 接口类型 | I 前缀 | `ICreateConversationParams`, `IResponseMessage` | +| 类型别名 | T 前缀或直接命名 | `TChatConversation`, `PresetAgentType` | +| 常量 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` | +| 工具函数 | camelCase | `formatMessage`, `parseResponse` | + +### 2.3 文件位置规范 + +``` +新增文件应放置于对应目录: + +src/ +├── agent/ # AI 代理实现 +│ ├── acp/ # ACP 协议代理 +│ ├── codex/ # Codex 代理 +│ └── gemini/ # Gemini 代理 +│ +├── common/ # 跨进程共享模块 +│ ├── adapters/ # API 适配器 +│ ├── types/ # 共享类型定义 +│ └── utils/ # 共享工具函数 +│ +├── process/ # Electron 主进程 +│ ├── bridge/ # IPC 桥接定义 (24+ 个) +│ ├── database/ # SQLite 数据库操作 +│ ├── services/ # 业务逻辑服务 +│ └── task/ # 任务管理 +│ +├── renderer/ # React 渲染进程 +│ ├── components/ # 可复用 UI 组件 +│ │ └── base/ # 基础组件 +│ ├── context/ # React Context 状态 +│ ├── hooks/ # 自定义 Hooks (31+) +│ ├── pages/ # 页面组件 +│ │ ├── conversation/ # 对话页面 +│ │ │ ├── preview/ # 预览面板 +│ │ │ └── workspace/ # 工作区 +│ │ ├── settings/ # 设置页面 (12+) +│ │ └── login/ # 登录页面 +│ ├── messages/ # 消息渲染组件 +│ ├── i18n/locales/ # 国际化文本 +│ ├── services/ # 前端服务 +│ └── utils/ # 前端工具函数 +│ +├── webserver/ # Web 服务器 (WebUI 模式) +│ ├── routes/ # API 路由 +│ └── middleware/ # 中间件 +│ +├── worker/ # Web Worker +│ +└── types/ # 全局类型定义 +``` + +### 2.4 代码风格 (Prettier 配置) + +```json +{ + "semi": true, // 使用分号 + "singleQuote": true, // 使用单引号 + "jsxSingleQuote": true, // JSX 使用单引号 + "trailingComma": "es5", // ES5 兼容的尾随逗号 + "tabWidth": 2, // 2 空格缩进 + "useTabs": false, // 不使用 Tab + "bracketSpacing": true, // 括号内空格 + "arrowParens": "always", // 箭头函数始终括号 + "endOfLine": "lf" // Unix 换行符 +} +``` + +### 2.5 质量要求 + +- [ ] TypeScript 类型完整,避免使用 `any` +- [ ] 使用 bridge 系统进行 IPC 通信 +- [ ] 实现错误边界处理 +- [ ] 支持国际化 (使用 i18next 的 `t()` 函数) +- [ ] 深色/浅色主题兼容 +- [ ] 响应式布局适配 + +### 2.6 禁止事项 + +- ❌ 直接使用 `ipcMain` / `ipcRenderer`,必须通过 bridge 系统 +- ❌ 在渲染进程直接访问 Node.js API +- ❌ 硬编码中文/英文文本,需使用 i18n key +- ❌ 使用内联样式,应使用 UnoCSS 类名 +- ❌ 在组件中直接操作 DOM,使用 React ref +- ❌ 忽略 TypeScript 错误 (`@ts-ignore`) + +--- + +## 3. 实现架构 + +### 3.1 分层架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 用户界面 (UI) │ +│ React 组件 / Hooks / Context │ +└─────────────────────┬───────────────────────────────────┘ + │ IPC Bridge +┌─────────────────────▼───────────────────────────────────┐ +│ 主进程 (Main) │ +│ Bridge → Service → Database / External API │ +└─────────────────────┬───────────────────────────────────┘ + │ +┌─────────────────────▼───────────────────────────────────┐ +│ 数据层 (Data) │ +│ SQLite / LocalStorage / External Services │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3.2 需要修改/新增的文件 + +**主进程 (src/process/)** + +| 文件路径 | 操作 | 说明 | +| -------- | ------------------- | ---- | +| | [ ] 新增 / [ ] 修改 | | + +**渲染进程 (src/renderer/)** + +| 文件路径 | 操作 | 说明 | +| -------- | ------------------- | ---- | +| | [ ] 新增 / [ ] 修改 | | + +**共享模块 (src/common/)** + +| 文件路径 | 操作 | 说明 | +| -------- | ------------------- | ---- | +| | [ ] 新增 / [ ] 修改 | | + +**类型定义 (src/types/)** + +| 文件路径 | 操作 | 说明 | +| -------- | ------------------- | ---- | +| | [ ] 新增 / [ ] 修改 | | + +### 3.3 IPC 通信设计 + +如需新增 IPC 通道,遵循以下模式: + +```typescript +// src/process/bridge/[功能]Bridge.ts +import { bridge } from '@anthropic/platform'; + +export const [功能名] = { + // Provider 模式: 请求-响应 (类似 HTTP 请求) + [方法名]: bridge.buildProvider('[通道名]'), + + // Emitter 模式: 事件流 (用于流式数据) + [事件名]: bridge.buildEmitter('[通道名].stream'), +}; + +// 使用示例: +// 渲染进程调用: const result = await [功能名].[方法名].request(params); +// 渲染进程监听: [功能名].[事件名].on((data) => { ... }); +``` + +### 3.4 状态管理设计 + +- [ ] 使用现有 Context: **\*\*\*\***\_\_\_\_**\*\*\*\*** +- [ ] 需要新增 Context: **\*\*\*\***\_\_\_\_**\*\*\*\*** +- [ ] 仅组件内部状态 (useState/useReducer) +- [ ] 需要持久化存储 + +### 3.5 国际化 Key 设计 + +```json +// 添加到 src/renderer/i18n/locales/[lang].json +// Key 命名规范: [模块].[功能].[描述] + +{ + "conversation.export.title": "导出对话", + "conversation.export.success": "导出成功", + "conversation.export.error": "导出失败" +} +``` + +**支持的语言文件:** + +- `zh-CN.json` - 简体中文 (必须) +- `en-US.json` - English (必须) +- `zh-TW.json` - 繁體中文 +- `ja-JP.json` - 日本語 +- `ko-KR.json` - 한국어 + +--- + +## 4. 验收标准 + +### 4.1 功能验收 + +- [ ] [具体功能点 1] +- [ ] [具体功能点 2] +- [ ] [具体功能点 3] + +### 4.2 边界情况 + +- [ ] [异常场景 1 的处理] +- [ ] [异常场景 2 的处理] + +### 4.3 兼容性验收 + +- [ ] macOS 正常运行 +- [ ] Windows 正常运行 +- [ ] 深色模式显示正确 +- [ ] 浅色模式显示正确 +- [ ] 多语言切换正常 + +### 4.4 代码质量 + +- [ ] `npm run lint` 无错误 +- [ ] `npm run build` 构建成功 +- [ ] TypeScript 无类型错误 +- [ ] 无 console.log 遗留 + +--- + +## 5. 参考资料 + +### 5.1 类似功能参考 + +[列出项目中可参考的类似实现] + +| 功能 | 文件路径 | 说明 | +| ---- | -------- | ---- | +| | | | + +### 5.2 依赖的现有模块 + +[列出需要调用的现有接口/组件/Hook] + +| 模块 | 路径 | 用途 | +| ---- | ---- | ---- | +| | | | + +### 5.3 外部依赖 + +[如需引入新依赖,列出并说明理由] + +| 依赖包 | 版本 | 用途 | 必要性说明 | +| ------ | ---- | ---- | ---------- | +| | | | | + +### 5.4 特殊注意事项 + +[列出实现过程中需要特别注意的事项] + +--- + +## 使用示例 + +以下是一个完整的功能需求示例: + +```markdown +## 1. 功能概述 + +### 1.1 基本信息 + +- **功能名称**: 对话导出 PDF +- **所属模块**: [x] 对话系统 +- **涉及进程**: [x] 主进程(process) [x] 渲染进程(renderer) + +### 1.2 功能描述 + +允许用户将当前对话导出为 PDF 文件,保留消息格式、代码高亮和图片。 + +### 1.3 用户场景 + +触发: 用户点击对话页面右上角的"导出"按钮,选择"导出为 PDF" +过程: 系统收集对话内容,渲染为 HTML,转换为 PDF +结果: 弹出保存对话框,用户选择保存位置后生成 PDF 文件 + +### 3.2 需要修改/新增的文件 + +**主进程 (src/process/)** +| 文件路径 | 操作 | 说明 | +|----------|------|------| +| src/process/bridge/exportBridge.ts | [x] 新增 | PDF 导出 IPC 通道定义 | +| src/process/services/ExportService.ts | [x] 新增 | PDF 生成逻辑 | + +**渲染进程 (src/renderer/)** +| 文件路径 | 操作 | 说明 | +|----------|------|------| +| src/renderer/pages/conversation/components/ChatHeader.tsx | [x] 修改 | 添加导出下拉菜单 | +| src/renderer/hooks/useExportPdf.ts | [x] 新增 | 导出功能 Hook | + +### 4.1 功能验收 + +- [ ] 点击导出按钮显示导出选项菜单 +- [ ] 选择 PDF 后弹出保存对话框 +- [ ] 生成的 PDF 包含完整对话内容 +- [ ] 代码块保留语法高亮样式 +- [ ] 图片正确嵌入 PDF + +### 5.1 类似功能参考 + +| 功能 | 文件路径 | 说明 | +| ------------- | ----------------------------------------------------- | --------------- | +| Markdown 导出 | src/renderer/hooks/useExportMarkdown.ts | 可参考导出流程 | +| PDF 预览 | src/renderer/pages/conversation/preview/PdfViewer.tsx | 可参考 PDF 处理 | +``` + +--- + +## 模板维护 + +- **创建日期**: 2025-01-27 +- **适用版本**: AionUi v0.x+ +- **维护者**: [项目团队] + +如需更新模板,请同步修改本文件并通知团队成员。 diff --git a/.claude/commands/package-assistant.md b/.claude/commands/package-assistant.md new file mode 100644 index 0000000..5901916 --- /dev/null +++ b/.claude/commands/package-assistant.md @@ -0,0 +1,174 @@ +# Package OfficeCLI Skill as AionUi Assistant + +Convert an OfficeCLI skill into a fully wired AionUi assistant preset, or update an existing one. + +## Usage + +``` +/package-assistant [assistant-id] [avatar] +``` + +- `` — Name of the skill directory under `/Users/veryliu/Documents/GitHub/officecli/skills/` (e.g. `officecli-docx`, `officecli-pptx`, `officecli-xlsx`, `officecli-data-dashboard`) +- `[assistant-id]` — Optional. ID for the new assistant (defaults to `-creator`) +- `[avatar]` — Optional. Emoji avatar (defaults to auto-selected based on skill type) + +Arguments: $ARGUMENTS + +--- + +## Before You Start — Detect Mode + +Before doing anything, check whether this skill has already been packaged: + +1. Check if `aionui/src/process/resources/skills//` exists +2. Check if the assistant-id already appears in `assistantPresets.ts` + +- **Both exist** → **Update mode**: Only re-copy skill files (Step 1). Skip Steps 2–4. +- **Neither exists** → **New mode**: Run all steps (1–5). +- **Skill exists but assistant doesn't** (or vice versa) → Run whichever steps are missing. + +--- + +## Step 1 — Copy Skill Files + +1. Read all files from `officecli/skills//` (SKILL.md, creating.md, editing.md, reference/, etc.) +2. Create target directory at `aionui/src/process/resources/skills//` — **directory name must be identical** to the officecli source directory name (both repos use the same `officecli-xxx` naming convention) +3. Copy all files, but apply these transformations to SKILL.md: + - **Remove version comments**: Delete any `# officecli: vX.X.X` line from inside the frontmatter. AionUi's frontmatter parser (`/^---\s*\n([\s\S]*?)\n---/`) requires clean YAML — version tracking comments break parsing and skills won't appear in the Skills Center. + - **Verify `name` field matches directory name**: The `name` field in SKILL.md frontmatter **MUST match the skill directory name exactly**. Both officecli and aionui use the same directory name (e.g. `officecli-docx`), so the `name` field should already be correct. If not, fix it. Mismatches cause: `params/name must be equal to one of the allowed values`. + - **Keep frontmatter fields**: `name` and `description` must stay intact + - **Keep BEFORE YOU START section**: The officecli install/update check section must be preserved — it's critical for users who don't have officecli installed +4. Copy creating.md, editing.md, and any other files (reference/ directories, etc.) as-is + +## Step 2 — Research the Skill (New Mode Only) + +Before writing descriptions and prompts, **study the skill thoroughly**: + +1. **Read the full skill content**: SKILL.md, creating.md, editing.md — understand what it uniquely does +2. **Identify differentiators**: What makes this skill different from other similar assistants already in aionui? The `descriptionI18n` must clearly communicate this so users can tell assistants apart at a glance +3. **Mine iteration test results**: Check `/Users/veryliu/Documents/GitHub/officecli/iterations/` for test reports related to this skill: + - Look for subdirectories matching the skill name (e.g. `data-dashboard/`, `xlsx/`, etc.) or related keywords + - Find documents/prompts that scored highest or produced the best results + - High-scoring test documents = prompts that are proven to produce good output → use these as `promptsI18n` + - If no exact match, check parent directories or README files for pointers +4. **Understand the output**: What does the skill produce? What does a "great" result look like? + +## Step 3 — Create Assistant Rule Files (New Mode Only) + +Create two rule files in `aionui/src/process/resources/assistant//`: + +**`.md`** (English): + +```markdown +# + +You are **** — an AI assistant that . + +## When the user greets you or asks what you can do + +Introduce yourself briefly: + +> <2-3 sentence self-introduction. Mention key capabilities, invite collaboration, acknowledge limitations honestly.> + +Then wait for the user's request. + +## When the user wants to + +Follow the `officecli-` skill exactly. It contains the complete workflow. Do not deviate from or simplify the skill's instructions. + +Before work starts, proactively remind the user once: + +> After the file appears in the workspace, you can preview it directly in AionUi. However, please do not click "Open with system app" while I'm still working, as this may lock the file and cause the operation to fail. + +After work completes, explicitly tell the user: + +> Your is ready. Please open it now to review. +``` + +**`.zh-CN.md`** (Chinese): +Same structure translated to natural Chinese. Keep the tone friendly and professional, not overly formal. + +## Step 4 — Add Preset Entry (New Mode Only) + +Add a new entry to `ASSISTANT_PRESETS` array in `aionui/src/common/config/presets/assistantPresets.ts`. + +**Placement**: New officecli-based assistants go at the **top** of the array (after morph-ppt). + +**Entry structure**: + +```typescript +{ + id: '', + avatar: '', + presetAgentType: 'gemini', + resourceDir: 'src/process/resources/assistant/', + ruleFiles: { + 'en-US': '.md', + 'zh-CN': '.zh-CN.md', + }, + defaultEnabledSkills: [''], + nameI18n: { + 'en-US': '', + 'zh-CN': '', + }, + descriptionI18n: { + 'en-US': '', + 'zh-CN': '', + }, + promptsI18n: { + 'en-US': [ + '<3 example prompts — prefer high-scoring prompts from iteration tests>', + ], + 'zh-CN': [ + '<3 Chinese example prompts — translated from the same high-scoring sources>', + ], + }, +}, +``` + +**Description writing rules**: + +- Users see multiple assistants side by side and decide which to use based on descriptions +- Each description must answer: "Why would I use THIS assistant instead of another?" +- Mention the specific output type, use case, and unique strength +- Avoid generic phrases like "professional documents" — be specific about what kind + +**Prompt selection rules**: + +- First priority: High-scoring prompts from `/Users/veryliu/Documents/GitHub/officecli/iterations/` +- These are battle-tested prompts that are proven to produce good results with the skill +- If no iteration data exists, write prompts that showcase the skill's unique strengths +- Prompts should be diverse (different use cases) and practical (things real users would ask) +- **CRITICAL: Prompts must be self-contained** — the user should be able to click the prompt and get a complete result without needing to provide any data, files, or attachments. Bad: "I have a CSV file, build a dashboard from it". Good: "Create a SaaS MRR dashboard with 12 months of sample data showing growth trends and churn breakdown". The assistant should generate sample data or pick a topic on its own. + +## Step 5 — Verify + +1. Confirm SKILL.md frontmatter starts with `---` on line 1 (no content before it) +2. Confirm frontmatter contains `name:` and `description:` fields +3. Confirm no `# officecli:` version comment remains in the frontmatter +4. Confirm assistant rule files exist in both en-US and zh-CN (new mode only) +5. Confirm preset entry is added to assistantPresets.ts with correct paths (new mode only) + +--- + +## Important Notes + +- The `_builtin/office-cli` skill already handles officecli discovery, but each skill's "BEFORE YOU START" section provides skill-specific install guidance — keep both. +- Avatar selection guide: 📝 for docx/word, 📊 for pptx/slides, 📈 for xlsx/excel, 📉 for dashboards/data, ✨ for morph/animation +- All assistants use `presetAgentType: 'gemini'` as default +- Both officecli and aionui use the same `officecli-xxx` directory naming convention. The skill directory name, SKILL.md `name` field, and `defaultEnabledSkills` entry must all be identical (e.g. `officecli-docx`). Exception: `morph-ppt` does not use the prefix. + +## Existing Assistants Reference + +When packaging a new assistant, review existing officecli-based assistants to avoid description overlap: + +| ID | Skill | Focus | +| ----------------- | ------------------------ | ----------------------------------------------------- | +| morph-ppt | morph-ppt | Morph-animated presentations with visual styles | +| word-creator | officecli-docx | Word documents — reports, proposals, letters | +| academic-paper | officecli-academic-paper | Formal academic papers — TOC, equations, bibliography | +| ppt-creator | officecli-pptx | General PPT creation, editing, analysis | +| excel-creator | officecli-xlsx | Excel — financial models, trackers, formulas | +| dashboard-creator | officecli-data-dashboard | CSV → Excel dashboards with KPI, charts, auto-scaling | + +Update this table when adding new assistants. diff --git a/.claude/skills/architecture/SKILL.md b/.claude/skills/architecture/SKILL.md new file mode 100644 index 0000000..1e042e5 --- /dev/null +++ b/.claude/skills/architecture/SKILL.md @@ -0,0 +1,141 @@ +--- +name: architecture +description: | + Project architecture and file structure conventions for all process types. + Use when: (1) Creating new files or modules, (2) Deciding where code should go, + (3) Converting single-file components to directories, (4) Reviewing code for structure compliance, + (5) Adding new bridges, services, agents, or workers. +--- + +# Architecture Skill + +Determine correct file placement and structure for an Electron multi-process project. + +## Detailed References + +- **Renderer layer** (components, hooks, utils, pages, CSS): [references/renderer.md](references/renderer.md) +- **Main process & shared layer** (bridges, services, worker, preload): [references/process.md](references/process.md) +- **Project root & monorepo layout** (directory structure, migration status): [references/project-layout.md](references/project-layout.md) + +--- + +## Decision Tree — Where Does New Code Go? + +``` +Is it UI (React components, hooks, pages)? + └── YES → packages/desktop/src/renderer/ → see references/renderer.md + +Is it an IPC handler responding to renderer calls? + └── YES → packages/desktop/src/process/bridge/ → see references/process.md + +Is it business logic running in the main process? + └── YES → packages/desktop/src/process/services/ → see references/process.md + +Is it an AI platform connection (API client, message protocol)? + └── YES → packages/desktop/src/process/agent// + +Is it a background task that runs in a worker thread? + └── YES → packages/desktop/src/process/worker/ + +Is it used by BOTH main and renderer processes? + └── YES → packages/desktop/src/common/ + +Is it an HTTP/WebSocket endpoint? + └── YES → packages/desktop/src/process/webserver/ + +Is it a plugin/extension resolver or loader? + └── YES → packages/desktop/src/process/extensions/ + +Is it a messaging channel (Lark, DingTalk, Telegram)? + └── YES → packages/desktop/src/process/channels/ +``` + +--- + +## Process Boundary Rules + +**Hard rules — violating them causes runtime crashes.** + +| Process | Can use | Cannot use | +| --------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- | +| **Main** (`packages/desktop/src/process/`) | Node.js, Electron main APIs, `fs`, `path`, `child_process` | DOM APIs (`document`, `window`, React) | +| **Renderer** (`packages/desktop/src/renderer/`) | DOM APIs, React, browser APIs | Node.js APIs (`fs`, `path`), Electron main APIs | +| **Worker** (`packages/desktop/src/process/worker/`) | Node.js APIs | DOM APIs, Electron APIs | +| **Preload** (`packages/desktop/src/preload/`) | `contextBridge`, `ipcRenderer` | DOM manipulation, Node.js `fs` | + +Cross-process communication: + +- Main ↔ Renderer: IPC via `packages/desktop/src/preload/` + `packages/desktop/src/process/bridge/*.ts` +- Main ↔ Worker: fork protocol via `packages/desktop/src/process/worker/WorkerProtocol.ts` + +```typescript +// NEVER in renderer +import { something } from '@process/services/foo'; // crashes at runtime + +// Use IPC instead +const result = await window.api.someMethod(); // goes through preload +``` + +--- + +## Naming Conventions + +### Directories + +| Scope | Convention | Reason | +| ---------------------------------- | ---------- | ------------------------------------------------------- | +| **Renderer** component/module dirs | PascalCase | React convention — dir name = component name | +| **Everything else** | lowercase | Node.js convention | +| **Categorical dirs** (everywhere) | lowercase | `components/`, `hooks/`, `utils/`, `services/` | +| **Platform dirs** (everywhere) | lowercase | `acp/`, `codex/`, `gemini/` — cross-process consistency | + +> Quick test: "Inside `packages/desktop/src/renderer/` AND represents a specific component/feature (not a category)?" → PascalCase. Otherwise → lowercase. + +### Files + +| Content | Convention | Examples | +| ------------------------- | ------------------------------- | ------------------------------------- | +| React components, classes | PascalCase | `SettingsModal.tsx`, `CronService.ts` | +| Hooks | camelCase with `use` prefix | `useTheme.ts`, `useCronJobs.ts` | +| Utilities, helpers | camelCase | `formatDate.ts`, `cronUtils.ts` | +| Entry points | `index.ts` / `index.tsx` | Required for directory-based modules | +| Config, types, constants | camelCase | `types.ts`, `constants.ts` | +| Styles | kebab-case or `Name.module.css` | `chat-layout.css` | + +--- + +## Structural Rules + +1. **Directory size limit**: Max **10** direct children. Split into subdirectories by responsibility when approaching. +2. **No single-file directories**: Merge into parent or related directory. +3. **Single file vs directory**: If a component needs a private sub-component or hook, convert to a directory with `index.tsx`. +4. **Page-private first**: Start code in `pages//`. Promote to shared only when a second consumer appears. + +## Test File Mapping + +Tests mirror source files in `tests/` subdirectories: + +| Source | Test | +| ------------------------------------------------------------ | ----------------------------------------------- | +| `packages/desktop/src/process/services/CronService.ts` | `tests/unit/cronService.test.ts` | +| `packages/desktop/src/renderer/hooks/ui/useAutoScroll.ts` | `tests/unit/useAutoScroll.dom.test.ts` | +| `packages/desktop/src/process/extensions/ExtensionLoader.ts` | `tests/unit/extensions/extensionLoader.test.ts` | + +When `tests/unit/` exceeds 10 direct children, group into subdirectories matching source structure. + +--- + +## Quick Checklist + +- [ ] Code is in the correct process directory (no cross-process imports) +- [ ] Renderer code does not use Node.js APIs +- [ ] Main process code does not use DOM APIs +- [ ] New IPC channels are bridged through `preload.ts` +- [ ] Renderer component/module dirs use PascalCase; categorical dirs use lowercase +- [ ] Platform dirs use lowercase everywhere +- [ ] Directory-based modules have `index.tsx` / `index.ts` entry point +- [ ] Page-private code is under `pages//`, not in shared dirs +- [ ] No single-file directories +- [ ] No directory exceeds 10 direct children +- [ ] New source files are auto-included in coverage — verify they are not accidentally excluded in `vitest.config.ts` → `coverage.exclude` +- [ ] New services separate pure logic from IO diff --git a/.claude/skills/architecture/references/process.md b/.claude/skills/architecture/references/process.md new file mode 100644 index 0000000..52ae011 --- /dev/null +++ b/.claude/skills/architecture/references/process.md @@ -0,0 +1,118 @@ +# Main Process & Shared Layer + +## `packages/desktop/src/process/` Structure + +``` +packages/desktop/src/process/ +├── bridge/ # IPC handlers — one file per domain +│ ├── index.ts # Registers all bridges +│ └── *Bridge.ts # Individual bridge files +├── services/ # Business logic services +│ ├── cron/ # Complex service → subdirectory +│ └── mcp-services/ +├── database/ # SQLite layer — schema, migrations, repositories +├── task/ # Agent/task management — managers, factories +├── utils/ # Main-process-only utilities +└── i18n/ # Main-process i18n +``` + +## Naming Conventions + +| Type | Pattern | Examples | +| ----------------- | ------------------------------- | --------------------------------- | +| Bridge | `Bridge.ts` (camelCase) | `cronBridge.ts`, `webuiBridge.ts` | +| Service | `Service.ts` (PascalCase) | `CronService.ts`, `McpService.ts` | +| Service interface | `IService.ts` | `IConversationService.ts` | +| Repository | `Repository.ts` | `SqliteConversationRepository.ts` | +| Agent Manager | `AgentManager.ts` | `AcpAgentManager.ts` | + +All directories use lowercase (Node.js convention): + +``` +packages/desktop/src/process/ +├── bridge/ # lowercase +├── services/ # lowercase +│ ├── cron/ # lowercase +│ └── mcp-services/ # lowercase (kebab-case for multi-word) +├── database/ # lowercase +└── task/ # lowercase +``` + +## Adding a New IPC Bridge + +1. Create `packages/desktop/src/process/bridge/Bridge.ts` +2. Register in `packages/desktop/src/process/bridge/index.ts` +3. Expose channel in `packages/desktop/src/preload/` +4. Add renderer-side types if needed + +## Adding a New Service + +- Simple → single file in `packages/desktop/src/process/services/` +- Complex (multiple files) → subdirectory: `packages/desktop/src/process/services//` + +## Service Testability Rules + +### Pure Logic vs IO Separation + +- **Pure logic** (transformation, validation, formatting) → standalone functions, no `fs`/`db`/`net` +- **IO operations** (file read, DB query, HTTP call) → thin wrappers in service class or repository +- Service methods should receive IO results as parameters + +### Dependency Injection + +```typescript +// ❌ Hard to test +import { db } from '@process/database'; +function getConversation(id: string) { + return db.query('SELECT * FROM conversations WHERE id = ?', id); +} + +// ✅ Easy to test +function getConversation(repo: IConversationRepository, id: string) { + return repo.findById(id); +} +``` + +For existing code using direct imports, `vi.mock()` is acceptable. For new code, prefer parameter injection. + +--- + +## Shared Layer + +### Preload (`packages/desktop/src/preload/`) + +IPC bridge between main and renderer. Uses `contextBridge` to expose safe APIs. + +- All main ↔ renderer communication goes through this file +- Only `contextBridge` and `ipcRenderer` APIs allowed +- No DOM manipulation, no Node.js `fs` + +### Common (`packages/desktop/src/common/`) + +Code imported by **both** main and renderer processes. + +- **Belongs**: shared types, API adapters, protocol converters, storage keys +- **Does NOT belong**: React components → `renderer/`, Node.js-specific → `process/` + +### Agent (`packages/desktop/src/process/agent/`) + +One directory per AI platform (lowercase): `acp/`, `codex/`, `gemini/`, `nanobot/`, `openclaw/`. Each has `index.ts` entry. Runs in main or worker process. + +### Worker (`packages/desktop/src/process/worker/`) + +``` +packages/desktop/src/process/worker/ +├── fork/ # Fork management +├── .ts # One file per agent platform (lowercase) +├── WorkerProtocol.ts # Protocol definition (PascalCase — it's a class) +└── index.ts +``` + +### Other Modules + +| Module | Location | Purpose | +| ---------- | ------------------------------------------ | -------------------------------------------------- | +| Channels | `packages/desktop/src/process/channels/` | Multi-channel messaging (Lark, DingTalk, Telegram) | +| Extensions | `packages/desktop/src/process/extensions/` | Plugin loading, resolvers, sandbox | +| WebServer | `packages/desktop/src/process/webserver/` | Express + WebSocket for WebUI | +| Adapter | `packages/desktop/src/common/adapter/` | Platform adapters (browser vs main environment) | diff --git a/.claude/skills/architecture/references/project-layout.md b/.claude/skills/architecture/references/project-layout.md new file mode 100644 index 0000000..e8747b0 --- /dev/null +++ b/.claude/skills/architecture/references/project-layout.md @@ -0,0 +1,79 @@ +# Project Layout + +## Root Directory + +### Rules + +- **Workspace root stays minimal**: root keeps shared config, scripts, tests, docs, assets, and package manager files. +- **Desktop app source lives under `packages/desktop/`**: do not add new app runtime code back to the root. +- **README translations** → `docs/readme/`, not root. Only main `readme.md` stays at root. +- **Guide documents** (`*_GUIDE.md`, `CODE_STYLE.md`) → `docs/` +- **Build artifacts** (`out/`, `node_modules/`) are gitignored + +### Current Root Structure (M1) + +``` +project-root/ +├── packages/ +│ └── desktop/ # Electron desktop workspace +├── tests/ # Shared test suites +├── docs/ # All documentation +├── scripts/ # Build and tooling scripts +├── resources/ # Static resources (icons, images, installers) +├── public/ # Shared Vite public assets +├── patches/ # npm/bun patches +├── homebrew/ # Homebrew formula +├── package.json # Workspace root config +├── tsconfig.json # Shared TS config +├── vitest.config.ts # Shared test config +├── AGENTS.md # Agent conventions +├── CLAUDE.md # Claude-specific config +└── ... # Other root-level tooling config +``` + +> **Migration rule**: New desktop runtime modules go under `packages/desktop/`, not the repository root. + +--- + +## `packages/desktop/` Layout + +### Workspace Structure + +``` +packages/desktop/ +├── src/ +│ ├── renderer/ # Renderer layer — React UI, no Node.js APIs +│ ├── process/ # Main process layer — Node.js / Electron business logic +│ ├── common/ # Shared cross-process code +│ ├── preload/ # IPC bridge entrypoints +│ ├── index.ts # Main process entry +│ └── types.d.ts # Ambient declarations +├── electron.vite.config.ts +├── electron-builder.yml +└── package.json +``` + +### `packages/desktop/src/` Structure + +``` +packages/desktop/src/ +├── renderer/ # React UI, browser-only code +├── process/ # Electron main-process and worker code +│ ├── bridge/ # IPC handlers +│ ├── services/ # Business logic +│ ├── agent/ # AI platform connections +│ ├── channels/ # Multi-channel messaging +│ ├── extensions/ # Plugin system +│ ├── webserver/ # WebUI server +│ └── worker/ # Background workers +├── common/ # Shared types, adapters, utilities +├── preload/ # contextBridge / ipcRenderer exposure +├── index.ts # Main process entry point +└── types.d.ts # Ambient declarations +``` + +### Placement Rules + +- New Electron runtime code belongs in `packages/desktop/src/**`. +- Root-level scripts and config may reference `packages/desktop/**`, but should not duplicate app source. +- Tests remain under `tests/**` and should reference desktop source through aliases or `packages/desktop/...` paths. diff --git a/.claude/skills/architecture/references/renderer.md b/.claude/skills/architecture/references/renderer.md new file mode 100644 index 0000000..ac4e171 --- /dev/null +++ b/.claude/skills/architecture/references/renderer.md @@ -0,0 +1,163 @@ +# Renderer Layer (`packages/desktop/src/renderer/`) + +## Root Directory — Standard Layout + +At most 3 entry files + 7 directories = 10 items: + +``` +packages/desktop/src/renderer/ +├── index.html # Vite HTML entry +├── main.tsx # React mount + app bootstrap +├── types.d.ts # Ambient type declarations +├── pages/ # Page-level modules (business code goes here) +├── components/ # Shared UI components (used across multiple pages) +├── hooks/ # Shared React hooks (supports business domain subdirs) +├── context/ # Global React contexts +├── services/ # Client-side services + i18n +├── utils/ # Utility functions + types + constants +├── styles/ # Global styles + theme configuration +└── assets/ # Static assets — Vite resolves to hashed URLs +``` + +**Does NOT belong at renderer root:** + +- CSS files → `styles/` +- Component files (`.tsx`) → `components/` or `pages/` +- Single-file directories → merge into a related directory + +## UI Library & Icon Standards + +- **Components**: `@arco-design/web-react` — use Arco components first +- **Icons**: `@icon-park/react` — all icons from this library +- **No raw HTML** for interactive elements (`