Add @multica/store and zustand to desktop dependencies. Replace
placeholder chat page with the shared Chat component. Add Toaster
for toast notifications and remove padding on the chat route.
Change Chat root from h-dvh to h-full for container adaptability.
Add showHeader prop to Chat; desktop passes showHeader={false}
since it has its own layout header.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| electron | ||
| src | ||
| .eslintrc.cjs | ||
| .gitignore | ||
| electron-builder.json5 | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
Multica Desktop App 设计文档
产品定位
Multica Desktop 是一个统一的桌面应用,具有双重身份:
- Host 模式: 本机运行 Hub + Agent,可供其他设备连接
- Client 模式: 连接到其他 Hub 的 Agent 进行对话
用户安装同一个 App,既可以作为 Agent 的宿主(让其他设备扫码连接),也可以扫码连接到别人的 Agent。
架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ Multica Desktop App │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ React UI (Renderer) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Home │ │ Chat │ │ Tools │ │ Skills │ │Settings │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ 直接调用 (本地) WebSocket (远程) │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ Local Hub + Agent │ │ Remote Hub (via Gateway) │ │
│ │ (进程内) │ │ (另一台设备) │ │
│ └─────────────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ WebSocket
▼
┌─────────────────────┐
│ Gateway │
│ (公网 WebSocket) │
└─────────────────────┘
关键点:
- 统一应用: 不区分 Admin App 和 Client App,一个 App 两种用法
- Chat 双模式: Chat 页面可以选择与本地 Agent 对话,或连接远程 Agent 对话
- 本地 Agent: Hub + Agent 跑在 Electron 主进程内,UI 通过 IPC 调用访问
- 远程连接: 通过 Gateway WebSocket 连接到其他设备的 Hub
约束: 第一阶段 1 Client - 1 Hub - 1 Agent Session
技术实现设计
技术栈
| 层级 | 技术 | 说明 |
|---|---|---|
| 框架 | Electron 30 | 桌面应用 |
| 前端 | React 19 + Vite | 渲染进程 |
| 路由 | react-router-dom v7 | HashRouter |
| 状态 | @multica/store (Zustand) | 复用现有 store |
| UI | @multica/ui (Shadcn) | 复用现有组件 |
| 二维码 | qrcode.react | 生成二维码 |
| 通信 | @multica/sdk | Gateway 连接 |
文件结构规划
apps/desktop/
├── electron/
│ ├── main.ts # 主进程 (Hub + Agent)
│ └── preload.ts # 预加载脚本 (如需 IPC)
├── src/
│ ├── main.tsx # React 入口
│ ├── App.tsx # 路由配置
│ ├── pages/
│ │ ├── home.tsx # Home 入口页 (三个选项)
│ │ ├── chat.tsx # Chat 页面 (Local/Remote 双模式)
│ │ ├── tools.tsx # Tools 管理页
│ │ ├── skills.tsx # Skills 管理页
│ │ └── layout.tsx # 全局布局 (Header + Tabs)
│ ├── components/
│ │ ├── qr-code.tsx # 二维码组件
│ │ ├── qr-scanner.tsx # 扫码组件
│ │ ├── connection-status.tsx # 连接状态
│ │ ├── tool-list.tsx # Tools 列表
│ │ └── skill-list.tsx # Skills 列表
│ └── hooks/
│ ├── use-local-agent.ts # 本地 Agent 管理
│ ├── use-remote-agent.ts # 远程 Agent 连接
│ └── use-connection.ts # 连接状态管理
└── package.json
核心实现点
1. 二维码生成与连接
二维码内容格式:
{
"type": "multica-connect",
"gateway": "wss://gateway.multica.ai",
"hubId": "019c1d32-xxxx",
"agentId": "019c1d32-yyyy",
"token": "random-uuid-token",
"expires": 1234567890
}
连接流程:
1. Admin 启动 → Hub 连接公网 Gateway → 注册为 deviceType: "hub"
2. Admin 创建 Agent → 生成 token → 编码到二维码 (含 hubId + agentId + token)
3. Client 扫码 → 解析二维码 → 连接同一 Gateway
4. Client 发送 "connect-request" 到 hubId (带 token)
5. Admin 验证 token 有效且未过期 → 建立配对关系
6. Client 后续消息发到 hubId,payload 带 agentId
7. Hub 路由消息到对应 Agent
2. Tools 管理
现有 CLI 命令 (已实现):
multica tools list # 列出所有 tools
multica tools list --profile coding # 按 profile 过滤
multica tools groups # 显示 tool groups
multica tools profiles # 显示预设 profiles
Admin App 实现方式 - 通过 IPC 调用 Main Process:
// Renderer 进程 (React Hook)
const tools = await window.electronAPI.tools.list();
const groups = await window.electronAPI.tools.getGroups();
const profiles = await window.electronAPI.tools.getProfiles();
await window.electronAPI.tools.setStatus('exec', false);
// Main 进程 (IPC Handler)
ipcMain.handle('tools:list', async () => {
const allTools = createAllTools(process.cwd());
return allTools.map((t) => ({
name: t.name,
group: TOOL_GROUPS[t.name],
enabled: true,
}));
});
注意: Renderer 进程运行在沙盒中,不能直接访问 Node.js API,必须通过 IPC 调用 Main Process。
3. Skills 管理
现有 CLI 命令 (已实现):
multica skills list # 列出所有 skills
multica skills status # 显示状态摘要
multica skills status <id> # 单个 skill 详情
multica skills add owner/repo # 从 GitHub 添加
multica skills remove <name> # 删除 skill
multica skills install <id> # 安装依赖
Admin App 实现方式 - 通过 IPC 调用 Main Process:
// Renderer 进程 (React Hook)
const skills = await window.electronAPI.skills.list();
await window.electronAPI.skills.add('anthropics/skills');
await window.electronAPI.skills.remove('pdf');
await window.electronAPI.skills.setEnabled('commit', false);
// Main 进程 (IPC Handler)
ipcMain.handle('skills:list', async () => {
return await listAllSkillsWithStatus();
});
ipcMain.handle('skills:add', async (_, source: string) => {
await addSkill({ source, force: false });
});
三、实现优先级
Phase 1: 基础框架 (MVP)
- Layout 组件 - Header + Tabs 导航
- Home 页面 - 二维码显示 + 连接状态
- Gateway 连接 - 复用 @multica/store
Phase 2: 管理功能
- Tools 页面 - 列表展示 + 开关切换
- Skills 页面 - 列表展示 + 基础操作
- Settings - Gateway URL + Theme
Phase 3: 完善体验
- Agent 页面 - 状态监控 + Provider 切换
- 二维码刷新机制
- 错误处理 + Toast 提示
四、Hub 集成技术方案
架构概述
Desktop App 采用 Electron IPC + Hub 实例 架构:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Electron Desktop App │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Renderer Process (React UI) │ │
│ │ │ │
│ │ home.tsx → useHub() → window.electronAPI.hub.getStatus() │ │
│ │ tools.tsx → useTools() → window.electronAPI.tools.list() │ │
│ │ skills.tsx→ useSkills()→ window.electronAPI.skills.list() │ │
│ │ │ │
│ └──────────────────────────────┬─────────────────────────────────────────┘ │
│ │ IPC (contextBridge) │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Main Process (Node.js) │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ Hub Instance │ │ │
│ │ │ - hubId: UUIDv7 │ │ │
│ │ │ - agents: Map<agentId, AsyncAgent> │ │ │
│ │ │ - status: 'starting' | 'ready' | 'error' │ │ │
│ │ │ - GatewayClient: 连接公网 Gateway (可选) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────────▼────────────────────────────────┐ │ │
│ │ │ AsyncAgent Instance │ │ │
│ │ │ - agentId: UUIDv7 │ │ │
│ │ │ - runner: AgentRunner (LLM interaction) │ │ │
│ │ │ - tools: Tool[] (可动态更新) │ │ │
│ │ │ - skills: SkillInfo[] │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
│ WebSocket (可选,用于 Client 远程连接)
▼
┌─────────────────────┐
│ Public Gateway │
│ (wss://xxx) │
└─────────────────────┘
IPC 通信机制
工作原理:
- Main Process: 在 Electron 主进程中创建 Hub 和 Agent 实例
- Preload Script: 通过
contextBridge.exposeInMainWorld暴露安全 API - Renderer Process: React UI 通过
window.electronAPI调用主进程功能
与 CLI 命令的关系:
| CLI 命令 | IPC Handler | 底层调用 |
|---|---|---|
multica tools list |
tools:list |
createAllTools() + getToolStatus() |
multica tools enable xxx |
tools:setStatus |
setToolStatus() |
multica skills list |
skills:list |
loadSkills() + listAllSkillsWithStatus() |
multica skills add xxx |
skills:add |
addSkill() |
本质上 CLI 和 Admin App 调用的是同一套底层模块,区别仅在于:
- CLI: 通过命令行参数解析后直接调用
- Admin App: 通过 IPC 转发调用
核心文件
apps/desktop/
├── electron/
│ ├── main.ts # 主进程入口,创建窗口 + 注册 IPC
│ ├── preload.ts # 暴露 electronAPI
│ └── ipc/
│ ├── index.ts # 统一注册所有 IPC handlers
│ ├── hub.ts # Hub 管理 (创建/状态/连接 Gateway)
│ ├── agent.ts # Agent 管理 (Tools 读写)
│ └── skills.ts # Skills 管理
├── src/
│ └── hooks/
│ ├── use-hub.ts # 获取 Hub 状态
│ ├── use-tools.ts # Tools CRUD
│ └── use-skills.ts # Skills CRUD
IPC 接口定义
// electron/preload.ts 暴露的 API
interface ElectronAPI {
hub: {
getStatus: () => Promise<HubStatus>;
getAgentInfo: () => Promise<AgentInfo | null>;
};
tools: {
list: () => Promise<ToolStatus[]>;
setStatus: (toolName: string, enabled: boolean) => Promise<void>;
getGroups: () => Promise<Record<string, string[]>>;
getProfiles: () => Promise<string[]>;
};
skills: {
list: () => Promise<SkillInfo[]>;
add: (source: string) => Promise<void>;
remove: (name: string) => Promise<void>;
setEnabled: (name: string, enabled: boolean) => Promise<void>;
};
}
// 类型定义
interface HubStatus {
hubId: string;
status: 'starting' | 'ready' | 'error';
agentCount: number;
gatewayConnected: boolean;
gatewayUrl?: string;
}
interface AgentInfo {
agentId: string;
provider: string;
model: string;
status: 'idle' | 'running';
}
interface ToolStatus {
name: string;
group: string;
enabled: boolean;
needsConfig?: boolean;
}
interface SkillInfo {
name: string;
command: string;
source: 'bundled' | 'global' | 'profile';
status: 'ready' | 'missing-deps' | 'disabled';
description?: string;
}
Hub 生命周期
// electron/ipc/hub.ts 简化逻辑
let hub: Hub | null = null;
export function registerHubHandlers(ipcMain: IpcMain) {
// App 启动时自动创建 Hub
ipcMain.handle('hub:getStatus', async () => {
if (!hub) {
hub = new Hub();
await hub.start();
// 创建默认 Agent
const agent = await hub.createAgent({
provider: credentialManager.getLlmProvider(),
model: credentialManager.getLlmProviderConfig()?.model,
});
}
return {
hubId: hub.id,
status: hub.status,
agentCount: hub.agents.size,
gatewayConnected: hub.gateway?.connected ?? false,
};
});
}
Tools 实时更新机制
当用户在 UI 中切换 Tool 开关时:
1. UI: Switch onChange → useTools.setToolStatus('exec', false)
2. Hook: await window.electronAPI.tools.setStatus('exec', false)
3. IPC: ipcMain.handle('tools:setStatus') → agent.updateTools(...)
4. Agent: 重新过滤 tools 列表,下次 LLM 调用使用新配置
注意: Tools 状态目前保存在内存中,重启后重置。后续可持久化到 ~/.super-multica/tool-config.json。
六、关于 RPC 与 IPC 的区别
问: Admin UI 和 Hub/Agent 之间是通过什么方式通信?
答: 通过 Electron IPC (进程间通信),不是网络 RPC。
| 通信类型 | 场景 | 协议 |
|---|---|---|
| IPC | Admin UI ↔ Hub (同一设备) | Electron IPC (内存) |
| RPC | Client ↔ Gateway ↔ Hub (跨设备) | WebSocket |
为什么选择 IPC 而不是直接 import?
- 安全隔离: Renderer 进程不应直接访问 Node.js API 和文件系统
- 进程隔离: Electron 推荐 Renderer 运行在沙盒中
- 一致性: 与 CLI 调用相同的底层模块,便于维护
- 扩展性: 后续可以轻松添加 RPC 支持,供远程管理
┌─────────────────────────────────────────────────────────────────┐
│ Electron App │
│ │
│ ┌──────────────────────┐ ┌─────────────────────────────┐ │
│ │ Renderer Process │ │ Main Process │ │
│ │ (React UI, 沙盒) │ │ (Node.js, 完整权限) │ │
│ │ │ IPC │ │ │
│ │ useTools() ──────────────► │ ipcMain.handle('tools:*') │ │
│ │ useSkills() ─────────────► │ ipcMain.handle('skills:*') │ │
│ │ useHub() ────────────────► │ Hub + Agent 实例 │ │
│ └──────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
IPC 调用示例:
// Renderer (React 组件)
const tools = await window.electronAPI.tools.list();
// Main Process (IPC Handler)
ipcMain.handle('tools:list', async () => {
const allTools = createAllTools(process.cwd());
return allTools.map((t) => ({
name: t.name,
group: TOOL_GROUPS[t.name] || 'other',
enabled: getToolStatus(t.name),
}));
});
七、依赖安装
# 二维码生成
pnpm --filter @multica/desktop add qrcode.react
# 类型定义 (如需要)
pnpm --filter @multica/desktop add -D @types/qrcode.react
八、实现步骤计划
Phase 1: 统一布局与路由重构
目标: 统一页面结构,移除 /admin 子路由
Step 1.1: 路由重构
- 重构
App.tsx路由- 移除
/admin子路由 - 统一页面结构: / (Home) / /chat / /tools / /skills
- 移除
- 创建
pages/layout.tsx- 全局布局- Header: Logo + 标题 + Settings 按钮
- Tabs: Home / Chat / Tools / Skills
- Content Area: 子路由出口
- 移动页面文件到根级别
Step 1.2: Home 页面 (三入口)
- 重构
pages/home.tsx- 左侧二维码 + 右侧 Agent 状态面板
- 底部: Open Chat 按钮 + Connect to Remote (Coming soon)
- 安装
qrcode.react依赖 - 创建
components/qr-code.tsx- 分享二维码组件- 生成二维码数据 (hubId, agentId, token, gateway, expires)
- 倒计时显示 + 自动过期刷新
- Refresh 按钮 + Copy Link 按钮
- 装饰性角落边框
Step 1.3: Chat 页面 (双模式)
- 重构
pages/chat.tsx- 顶部模式切换: Local Agent / Remote Agent
- 支持本地 Agent 直接调用
- 支持远程 Agent WebSocket 连接
- 创建
hooks/use-local-agent.ts- 本地 Agent 调用 - 创建
hooks/use-remote-agent.ts- 远程 Agent 连接
交付物: 统一的页面结构,Home 页面三入口可用
Phase 2: IPC 集成与 Hub 启动 ✅ (完成)
目标: 在 Main Process 中启动 Hub,通过 IPC 与 Renderer 通信
Step 2.1: IPC 基础设施
- 创建
electron/ipc/目录结构 - 创建
electron/ipc/index.ts- 统一注册 handlers - 创建
electron/ipc/agent.ts- Tools 相关 IPC handlers - 创建
electron/ipc/skills.ts- Skills 相关 IPC handlers - 更新
electron/main.ts- 注册 IPC handlers
Step 2.2: Hub 集成
- 创建
electron/ipc/hub.ts- Hub 管理 - 实现 Hub 自动启动 (App ready 时)
- 实现 Agent 自动创建
- 实现 Hub 状态查询 (
hub:getStatus)
Step 2.3: Preload 脚本
- 更新
electron/preload.ts- 暴露
window.electronAPI.hub.* - 暴露
window.electronAPI.tools.* - 暴露
window.electronAPI.skills.*
- 暴露
Step 2.4: Hooks 更新
- 更新
hooks/use-tools.ts- 调用 IPC - 更新
hooks/use-skills.ts- 调用 IPC - 创建
hooks/use-hub.ts- Hub 状态
交付物: Hub 在主进程运行,UI 可通过 IPC 获取真实数据
Phase 3: Tools 管理页面
目标: 查看和管理 Agent Tools
Step 3.1: Tools 数据获取
- 创建
hooks/use-tools.ts- 获取所有 tools 列表
- 获取 tool groups 和 profiles
- 管理 allow/deny 状态
Step 3.2: Tools UI 组件
- 创建
components/tool-list.tsx- 表格展示: Name / Group / Status / Toggle
- 按 Group 分组折叠
- 开关切换 (Switch 组件)
- Profile 下拉选择器 (内置)
- Reset to Default 按钮 (内置)
Step 3.3: Tools 页面整合
- 更新
pages/tools.tsx- Profile 选择器
- Tool 列表
- (状态持久化待后续实现)
Step 3.4: Tools 实时同步
- 实现
tools:list从真实 Agent 获取活跃 tools - 实现
tools:active获取当前活跃工具 - 实现
tools:reload调用 Agent.reloadTools() - 暴露 AsyncAgent.getActiveTools() 和 reloadTools() 方法
- 实现
tools:setStatus持久化到 profile config.json - 验证 Tool 开关影响 Agent 行为
交付物: 可查看所有 Tools,切换 Profile,开关单个 Tool,实时影响 Agent
Phase 4: Skills 管理页面
目标: 查看、添加、删除 Skills
Step 4.1: Skills 数据获取
- 创建
hooks/use-skills.ts- 加载所有 skills (mock data for now)
- 检查 eligibility
- 添加/删除/安装操作 (stub)
Step 4.2: Skills UI 组件
- 创建
components/skill-list.tsx- 表格展示: Name / Source / Status / Actions
- Status 徽章 (ready / missing / disabled)
- Action 按钮 (View / Install / Delete)
- Add Skill dialog (内置 skills.tsx)
- View Skill dialog (内置 skills.tsx)
Step 4.3: Skills 页面整合
- 更新
pages/skills.tsx- Skill 列表
- Add Skill 按钮 + dialog
- View Skill dialog
- Refresh 按钮
Step 4.4: Skills IPC 集成
- 在 Agent 中添加
getSkillsWithStatus()方法 - 在 AsyncAgent 中暴露
getSkillsWithStatus()方法 - 实现
skills:list从真实 Agent 获取 skills - 实现
skills:get获取单个 skill 详情 - 实现
skills:toggle返回当前 eligibility 状态 - 实现
skills:reload重新加载 skills - 实现
skills:add调用addSkill() - 实现
skills:remove调用removeSkill()
交付物: 可查看所有 Skills,查看 Skill 详情,显示 eligibility 状态
Phase 5: 设置与完善
目标: Settings 页面 + 体验优化
Step 5.1: Settings 页面
- 创建
components/settings-dialog.tsx- Gateway URL 配置
- Theme 切换 (Light / Dark / System)
- 打开 credentials.json5 按钮
Step 5.2: 连接状态管理
- 创建
components/connection-status.tsx- 显示 Gateway 连接状态
- 显示已连接的 Client 信息
- 显示 Agent 状态
Step 5.3: 体验优化
- Toast 通知 (操作成功/失败)
- Loading 状态优化 (各页面)
- 错误边界处理 (React Error Boundary)
- 二维码自动刷新 (5 分钟过期后自动刷新)
交付物: 完整的管理功能,良好的用户体验
Phase 6: Chat 页面与 Agent 联调
目标: 实现 Chat 功能,支持本地和远程 Agent
Step 6.1: 本地 Chat 实现
- 重构
pages/chat.tsx- 消息输入框 + 发送按钮
- 消息历史展示
- 流式响应显示
- 创建
hooks/use-local-agent.ts- 通过 IPC 调用 Agent.run()
- 处理流式响应
- 管理消息历史
Step 6.2: 远程 Chat 实现
- 创建
hooks/use-remote-agent.ts- 通过 Gateway WebSocket 连接
- 处理远程消息
- Chat 页面模式切换
- Local Mode / Remote Mode 切换
交付物: 可与本地 Agent 对话,可连接远程 Agent
Phase 7: 联调与测试
目标: 完整流程联调
Step 7.1: 本地 Agent 联调
- Tools 开关实时影响 Agent
- Skills 启用/禁用影响 Agent
- Chat 流式响应正常
Step 7.2: 远程连接联调
- 扫码连接远程 Agent
- Token 验证流程
- 消息流转测试
Step 7.3: 异常处理
- 断开重连
- Token 过期处理
- Gateway 断开处理
九、当前进度摘要
| Phase | 名称 | 状态 |
|---|---|---|
| Phase 1 | 布局与路由 | ✅ 完成 |
| Phase 2 | IPC 集成与 Hub | ✅ 完成 |
| Phase 3 | Tools 管理 | ✅ UI + IPC 集成完成 |
| Phase 4 | Skills 管理 | ✅ UI + IPC 集成完成 |
| Phase 5 | 设置与完善 | ⏳ 待开始 |
| Phase 6 | Chat 页面 | ⏳ 待开始 (同事负责 UI) |
| Phase 7 | 联调测试 | ⏳ 待开始 |