diff --git a/ClawX-项目架构与版本大纲.md b/ClawX-项目架构与版本大纲.md new file mode 100644 index 000000000..193cab3cc --- /dev/null +++ b/ClawX-项目架构与版本大纲.md @@ -0,0 +1,4296 @@ +# ClawX 项目架构与版本大纲 + +> 基于 OpenClaw 的图形化 AI 助手应用 +> 技术栈:Electron + React + TypeScript +> 代码规范:全部英文注释、 +> 开发规范:每一个模块开发完成后,写好完整单测,在{project}/build_process/目录中,更新proecess.md, 增加当前feature.md文档(保持commit_X_feat.md格式),提交commit。 +> 图形支持语言:与openClaw保持一致 +> 如果有疑问请重新参考当前文档 + +--- + +## 一、项目概述 + +### 1.1 项目定位 + +**ClawX** 是 OpenClaw 的图形化封装层,旨在提供: + +- 🎯 **零命令行体验** - 通过 GUI 完成所有安装、配置和使用 +- 🎨 **现代化 UI** - 美观、直观的桌面应用界面 +- 📦 **开箱即用** - 预装精选技能包,即刻可用 +- 🖥️ **跨平台** - macOS / Windows / Linux 统一体验 +- 🔄 **无缝集成** - 与 OpenClaw 生态完全兼容 + +### 1.2 目标用户 + +| 用户群体 | 痛点 | ClawX 解决方案 | +|----------|------|----------------| +| 非技术用户 | 命令行恐惧 | 可视化安装向导 | +| 效率追求者 | 配置繁琐 | 一键预设技能包 | +| 跨平台用户 | 体验不一致 | 统一 UI 设计语言 | +| AI 尝鲜者 | 门槛高 | 引导式 onboarding | + +### 1.3 与 OpenClaw 的关系 + +``` +┌─────────────────────────────────────────────────────────┐ +│ ClawX App │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ Electron Main Process │ │ +│ │ - 窗口管理、系统托盘、自动更新 │ │ +│ │ - Gateway 进程管理 │ │ +│ │ - Node.js 环境检测/安装 │ │ +│ └─────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ React Renderer Process │ │ +│ │ - 现代化 UI 界面 │ │ +│ │ - WebSocket 通信层 │ │ +│ └─────────────────────────────────────────────────┘ │ +└────────────────────────┬────────────────────────────────┘ + │ WebSocket (JSON-RPC) + ▼ +┌─────────────────────────────────────────────────────────┐ +│ OpenClaw Gateway (上游) │ +│ - 消息通道管理 (WhatsApp/Telegram/Discord...) │ +│ - AI Agent 运行时 │ +│ - 技能/插件系统 │ +└─────────────────────────────────────────────────────────┘ +``` + +**核心原则**: +- ✅ **封装而非 Fork** - 通过 npm 依赖引入 openclaw +- ✅ **不修改上游** - 所有定制通过配置、插件实现 +- ✅ **版本绑定** - 每个 ClawX 版本绑定特定 openclaw 版本 +- ✅ **CLI 兼容** - 命令行保持 `openclaw` 命令,不引入 `clawx` CLI + +openclaw project Local: /Users/guoyuliang/Project/openclaw remote: https://github.com/openclaw/openclaw +### 1.4 CLI 兼容性设计 + +ClawX 是 OpenClaw 的**图形化增强层**,而非替代品。用户可以同时使用 GUI 和 CLI: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ClawX + OpenClaw 共存模式 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 用户交互方式 │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ ClawX GUI │ │ openclaw CLI │ │ +│ │ (图形界面) │ │ (命令行) │ │ +│ │ │ │ │ │ +│ │ • 点击操作 │ │ • openclaw doctor │ │ +│ │ • 可视化配置 │ │ • openclaw plugins │ │ +│ │ • 安装向导 │ │ • openclaw config │ │ +│ │ • 普通用户首选 │ │ • 高级用户/脚本 │ │ +│ └──────────┬──────────┘ └──────────┬──────────┘ │ +│ │ │ │ +│ └────────────┬─────────────┘ │ +│ ▼ │ +│ ┌─────────────────────────┐ │ +│ │ OpenClaw Gateway │ │ +│ │ (共享同一实例) │ │ +│ └─────────────────────────┘ │ +│ │ │ +│ ┌────────────┴────────────┐ │ +│ ▼ ▼ │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ ~/.openclaw/ │ │ OpenClaw 配置/数据 │ │ +│ │ (共享配置目录) │ │ (技能/插件/会话) │ │ +│ └─────────────────────┘ └─────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### CLI 兼容性原则 + +| 原则 | 说明 | +|------|------| +| **命令一致** | 使用 `openclaw` 命令,不引入 `clawx` CLI | +| **配置共享** | GUI 和 CLI 共享 `~/.openclaw/` 配置目录 | +| **Gateway 共享** | GUI 和 CLI 连接同一个 Gateway 实例 | +| **功能互补** | GUI 简化常用操作,CLI 支持高级/自动化场景 | + +#### 用户使用场景 + +**场景 A: 纯 GUI 用户 (新手)** +``` +1. 安装 ClawX.app +2. 通过安装向导完成配置 +3. 日常使用 GUI 界面 +4. 无需接触命令行 +``` + +**场景 B: GUI + CLI 混合用户 (进阶)** +``` +1. 安装 ClawX.app (自动包含 openclaw CLI) +2. 日常使用 GUI 界面 +3. 需要时打开终端使用 CLI: + - openclaw doctor # 健康检查 + - openclaw plugins list # 查看插件 + - openclaw config get # 查看配置 +``` + +**场景 C: CLI 优先用户 (开发者)** +``` +1. 安装 ClawX.app 或单独安装 openclaw CLI +2. 主要使用命令行操作 +3. 偶尔打开 GUI 查看状态或配置复杂选项 +``` + +#### ClawX 安装时的 CLI 处理 + +```typescript +// electron/installer/cli-setup.ts + +/** + * ClawX 安装时确保 openclaw CLI 可用 + * 不创建 clawx 命令,保持与上游一致 + */ +export async function ensureOpenClawCli(): Promise { + const isInstalled = await checkCliInstalled('openclaw'); + + if (!isInstalled) { + // 通过 npm 全局安装 openclaw + await privilegeManager.execAsAdmin( + 'npm install -g openclaw', + { reason: '安装 OpenClaw 命令行工具' } + ); + } + + // 确保 PATH 包含 npm 全局目录 + await pathManager.ensureNpmGlobalPath(); + + // 验证安装 + const version = await exec('openclaw --version'); + console.log(`OpenClaw CLI installed: ${version}`); +} + +/** + * ClawX 不创建自己的 CLI 命令 + * 所有命令行操作都通过 openclaw 完成 + */ +// ❌ 不会有 clawx CLI +// ✅ 只有 openclaw CLI +``` + +#### GUI 与 CLI 的功能映射 + +| 操作 | ClawX GUI | openclaw CLI | +|------|-----------|--------------| +| 健康检查 | 设置 → 诊断 → 运行检查 | `openclaw doctor` | +| 安装插件 | 技能市场 → 安装 | `openclaw plugins install ` | +| 查看配置 | 设置 → 高级 | `openclaw config get` | +| 修改配置 | 设置页面表单 | `openclaw config set ` | +| 查看状态 | Dashboard | `openclaw status` | +| 查看日志 | 设置 → 日志 | `openclaw logs` | +| 连接通道 | 通道 → 添加 → 扫码 | `openclaw channels add whatsapp` | +| 发送消息 | 对话界面 | `openclaw message send ` | + +#### 开发者模式: 终端集成 + +```typescript +// src/pages/Settings/DeveloperSettings.tsx + +export function DeveloperSettings() { + const openTerminal = async () => { + // 在内置终端或系统终端中打开,预设好环境 + await window.electron.ipcRenderer.invoke('terminal:open', { + cwd: '~/.openclaw', + env: { + // 确保 openclaw 命令可用 + PATH: `${process.env.PATH}:${npmGlobalBinPath}`, + }, + }); + }; + + return ( + + + 开发者工具 + + + {/* 快捷命令按钮 */} +
+ + + + +
+ + + + {/* 打开终端 */} + + +

+ ClawX 与 OpenClaw CLI 完全兼容,您可以在终端中使用 openclaw 命令进行高级操作。 +

+
+
+ ); +} +``` + +#### 配置同步机制 + +```typescript +// electron/config/sync.ts + +/** + * ClawX 和 openclaw CLI 共享同一配置文件 + * 任何一方的修改都会实时反映到另一方 + */ +export class ConfigSync { + private configPath = join(homedir(), '.openclaw', 'config.json'); + private watcher: FSWatcher | null = null; + + /** + * 监听配置文件变化 (CLI 修改时同步到 GUI) + */ + startWatching(): void { + this.watcher = watch(this.configPath, async () => { + const config = await this.readConfig(); + // 通知渲染进程配置已更新 + mainWindow?.webContents.send('config:updated', config); + }); + } + + /** + * GUI 修改配置时,写入共享配置文件 (CLI 可读取) + */ + async updateConfig(updates: Partial): Promise { + const current = await this.readConfig(); + const merged = deepMerge(current, updates); + await writeFile(this.configPath, JSON.stringify(merged, null, 2)); + } + + /** + * 配置文件位置说明 + * + * ~/.openclaw/ + * ├── config.json # 主配置 (GUI 和 CLI 共享) + * ├── credentials/ # 凭证存储 + * ├── sessions/ # 会话数据 + * └── skills/ # 用户技能 + * + * ~/.clawx/ + * ├── presets.json # ClawX 专属配置 (技能包选择等) + * └── ui-state.json # GUI 状态 (窗口位置等) + */ +} +``` + +--- + +## 二、技术架构 + +### 2.1 技术栈选型 + +| 层级 | 技术 | 版本 | 选型理由 | +|------|------|------|----------| +| **运行时** | Electron | 33+ | 跨平台、嵌入 Node.js | +| **前端框架** | React | 19 | 生态丰富、hooks 模式 | +| **UI 组件** | shadcn/ui | latest | 可定制、现代设计 | +| **样式** | Tailwind CSS | 4.x | 原子化、快速开发 | +| **状态管理** | Zustand | 5.x | 轻量、TypeScript 友好 | +| **路由** | React Router | 7.x | 声明式、嵌套路由 | +| **构建工具** | Vite | 6.x | 极速 HMR | +| **打包工具** | electron-builder | latest | 多平台打包、自动更新 | +| **测试** | Vitest + Playwright | latest | 单元测试 + E2E | +| **语言** | TypeScript | 5.x | 类型安全 | + +### 2.2 双端口架构与开发者模式 + +ClawX 采用**双端口分层架构**,区分普通用户界面与开发者管理后台: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 用户访问入口 │ +├─────────────────────────────┬───────────────────────────────────┤ +│ │ │ +│ ┌─────────────────────┐ │ ┌─────────────────────────┐ │ +│ │ ClawX GUI │ │ │ OpenClaw Control UI │ │ +│ │ Port: 23333 │ │ │ Port: 18789 │ │ +│ │ │ │ │ │ │ +│ │ 🎨 现代化界面 │ │ │ ⚙️ 原生管理后台 │ │ +│ │ 📦 预装技能包 │──▶│ │ 🔧 高级配置 │ │ +│ │ 🚀 一键安装向导 │ │ │ 📊 调试日志 │ │ +│ │ 👤 普通用户 │ │ │ 👨‍💻 开发者/高级用户 │ │ +│ └─────────────────────┘ │ └─────────────────────────┘ │ +│ │ │ +│ [开发者模式] ────────┼──────────────▶ │ +│ │ │ +└─────────────────────────────┴───────────────────────────────────┘ + │ + │ WebSocket (JSON-RPC) + ▼ + ┌────────────────────────┐ + │ OpenClaw Gateway │ + │ 内部服务端口: 18789 │ + └────────────────────────┘ +``` + +#### 端口分配 + +| 端口 | 服务 | 用途 | 目标用户 | +|------|------|------|----------| +| **23333** | ClawX GUI | 默认图形化界面 | 所有用户 | +| **18789** | OpenClaw Control UI | Gateway 管理后台 | 开发者/高级用户 | + +> **端口选择说明**: +> - `23333` - ClawX 专属端口,易记且不与常见服务冲突 +> - `18789` - OpenClaw 原生端口,保持上游兼容 + +#### 开发者模式入口 + +```typescript +// src/components/layout/Sidebar.tsx +export function Sidebar() { + const [devModeClicks, setDevModeClicks] = useState(0); + + // 连续点击 5 次版本号解锁开发者模式 + const handleVersionClick = () => { + const clicks = devModeClicks + 1; + setDevModeClicks(clicks); + + if (clicks >= 5) { + toast.success('开发者模式已解锁'); + setDevModeClicks(0); + } + }; + + return ( + + ); +} +``` + +#### 设置页面快捷入口 + +```typescript +// src/pages/Settings/AdvancedSettings.tsx +export function AdvancedSettings() { + return ( + + + 高级设置 + + + {/* 其他设置项 */} + + + + {/* 开发者工具区域 */} +
+ +

+ 访问 OpenClaw 原生管理后台,进行高级配置和调试 +

+ +

+ 将在浏览器中打开 http://localhost:18789 +

+
+
+
+ ); +} +``` + +#### 配置文件 + +```typescript +// electron/utils/config.ts +export const PORTS = { + /** ClawX 默认 GUI 端口 */ + CLAWX_GUI: 23333, + + /** OpenClaw Gateway 端口 (上游默认) */ + OPENCLAW_GATEWAY: 18789, +} as const; + +// 环境变量覆盖 +export function getPort(key: keyof typeof PORTS): number { + const envKey = `CLAWX_PORT_${key}`; + const envValue = process.env[envKey]; + return envValue ? parseInt(envValue, 10) : PORTS[key]; +} +``` + +#### 使用场景对比 + +| 场景 | ClawX GUI (23333) | OpenClaw 后台 (18789) | +|------|-------------------|----------------------| +| 日常对话 | ✅ | ❌ | +| 查看消息记录 | ✅ | ✅ | +| 添加/管理通道 | ✅ (简化版) | ✅ (完整版) | +| 安装技能包 | ✅ | ❌ | +| 编辑技能配置 | ❌ | ✅ | +| 查看原始日志 | ❌ | ✅ | +| 插件管理 | ❌ | ✅ | +| JSON 配置编辑 | ❌ | ✅ | +| WebSocket 调试 | ❌ | ✅ | +| Cron 任务管理 | ✅ | ✅ | + +### 2.3 目录结构 + +``` +clawx/ +├── .github/ +│ ├── workflows/ +│ │ ├── ci.yml # 持续集成 +│ │ ├── release.yml # 自动发布 +│ │ └── test.yml # 测试流水线 +│ └── ISSUE_TEMPLATE/ +│ +├── electron/ # Electron 主进程 +│ ├── main/ +│ │ ├── index.ts # 主入口 +│ │ ├── window.ts # 窗口管理 +│ │ ├── tray.ts # 系统托盘 +│ │ ├── menu.ts # 原生菜单 +│ │ └── ipc-handlers.ts # IPC 处理器 +│ │ +│ ├── updater/ # 自动更新模块 +│ │ ├── index.ts # 更新管理器入口 +│ │ ├── checker.ts # 版本检查器 +│ │ ├── downloader.ts # 下载管理 +│ │ ├── notifier.ts # 更新通知 +│ │ └── channels.ts # 更新通道配置 +│ │ +│ ├── gateway/ +│ │ ├── manager.ts # Gateway 进程生命周期 +│ │ ├── client.ts # WebSocket 客户端 +│ │ ├── protocol.ts # JSON-RPC 协议定义 +│ │ └── health.ts # 健康检查 +│ │ +│ ├── installer/ +│ │ ├── node-installer.ts # Node.js 自动安装 +│ │ ├── openclaw-installer.ts # openclaw npm 安装 +│ │ ├── skill-installer.ts # 技能包安装 +│ │ └── platform/ +│ │ ├── darwin.ts # macOS 特定逻辑 +│ │ ├── win32.ts # Windows 特定逻辑 +│ │ └── linux.ts # Linux 特定逻辑 +│ │ +│ ├── privilege/ # 权限提升模块 +│ │ ├── index.ts # 统一权限管理器 +│ │ ├── darwin-admin.ts # macOS 管理员权限 (osascript) +│ │ ├── win32-admin.ts # Windows UAC 提升 +│ │ └── linux-admin.ts # Linux pkexec/polkit +│ │ +│ ├── env-config/ # 环境配置模块 +│ │ ├── index.ts # 环境配置管理器 +│ │ ├── path-manager.ts # PATH 环境变量管理 +│ │ ├── api-keys.ts # API Key 安全存储 +│ │ └── shell-profile.ts # Shell 配置文件管理 +│ │ +│ ├── preload/ +│ │ └── index.ts # 预加载脚本 (contextBridge) +│ │ +│ └── utils/ +│ ├── logger.ts # 日志 +│ ├── paths.ts # 路径管理 +│ └── store.ts # 持久化存储 (electron-store) +│ +├── src/ # React 渲染进程 +│ ├── main.tsx # React 入口 +│ ├── App.tsx # 根组件 +│ │ +│ ├── pages/ # 页面组件 +│ │ ├── Dashboard/ +│ │ │ ├── index.tsx +│ │ │ ├── StatusCard.tsx +│ │ │ └── QuickActions.tsx +│ │ ├── Chat/ +│ │ │ ├── index.tsx +│ │ │ ├── MessageList.tsx +│ │ │ ├── InputArea.tsx +│ │ │ └── ToolCallCard.tsx +│ │ ├── Channels/ +│ │ │ ├── index.tsx +│ │ │ ├── ChannelCard.tsx +│ │ │ └── QRScanner.tsx +│ │ ├── Skills/ +│ │ │ ├── index.tsx +│ │ │ ├── SkillCard.tsx +│ │ │ ├── SkillMarket.tsx +│ │ │ └── BundleSelector.tsx + │ │ ├── Cron/ # 定时任务 +│ │ │ ├── index.tsx # 任务列表页 +│ │ │ ├── CronJobCard.tsx # 任务卡片组件 +│ │ │ ├── CronEditor.tsx # 任务编辑器 +│ │ │ ├── CronSchedulePicker.tsx # Cron 表达式选择器 +│ │ │ └── CronHistory.tsx # 执行历史 +│ │ ├── Settings/ +│ │ │ ├── index.tsx +│ │ │ ├── GeneralSettings.tsx +│ │ │ ├── ProviderSettings.tsx +│ │ │ └── AdvancedSettings.tsx +│ │ └── Setup/ # 安装向导 +│ │ ├── index.tsx +│ │ ├── WelcomeStep.tsx +│ │ ├── RuntimeStep.tsx +│ │ ├── ProviderStep.tsx +│ │ ├── ChannelStep.tsx +│ │ └── SkillStep.tsx +│ │ +│ ├── components/ # 通用组件 +│ │ ├── ui/ # shadcn/ui 组件 +│ │ │ ├── button.tsx +│ │ │ ├── card.tsx +│ │ │ ├── dialog.tsx +│ │ │ └── ... +│ │ ├── layout/ +│ │ │ ├── Sidebar.tsx +│ │ │ ├── Header.tsx +│ │ │ └── MainLayout.tsx +│ │ └── common/ +│ │ ├── LoadingSpinner.tsx +│ │ ├── ErrorBoundary.tsx +│ │ └── StatusBadge.tsx +│ │ +│ ├── hooks/ # 自定义 Hooks +│ │ ├── useGateway.ts # Gateway 连接状态 +│ │ ├── useChannels.ts # 通道数据 +│ │ ├── useSkills.ts # 技能数据 +│ │ ├── useChat.ts # 聊天会话 +│ │ ├── useCron.ts # 定时任务数据 +│ │ └── useElectron.ts # IPC 通信 +│ │ +│ ├── stores/ # Zustand 状态管理 +│ │ ├── gateway.ts # Gateway 状态 +│ │ ├── channels.ts # 通道状态 +│ │ ├── chat.ts # 聊天状态 +│ │ ├── skills.ts # 技能状态 +│ │ ├── cron.ts # 定时任务状态 +│ │ └── settings.ts # 设置状态 +│ │ +│ ├── services/ # 服务层 +│ │ ├── gateway-rpc.ts # Gateway RPC 调用封装 +│ │ ├── skill-service.ts # 技能服务 +│ │ ├── cron-service.ts # 定时任务服务 +│ │ └── update-service.ts # 更新服务 +│ │ +│ ├── types/ # TypeScript 类型定义 +│ │ ├── gateway.ts # Gateway 协议类型 +│ │ ├── channel.ts # 通道类型 +│ │ ├── skill.ts # 技能类型 +│ │ ├── cron.ts # 定时任务类型 +│ │ └── electron.d.ts # Electron API 类型 +│ │ +│ ├── utils/ # 工具函数 +│ │ ├── format.ts +│ │ └── platform.ts +│ │ +│ └── styles/ +│ ├── globals.css # 全局样式 +│ └── themes/ # 主题定义 +│ ├── light.css +│ └── dark.css +│ +├── resources/ # 静态资源 +│ ├── icons/ # 应用图标 +│ │ ├── icon.icns # macOS +│ │ ├── icon.ico # Windows +│ │ └── icon.png # Linux +│ ├── skills/ # 预装技能包 +│ │ ├── productivity.json +│ │ ├── developer.json +│ │ └── smart-home.json +│ └── locales/ # 国际化 (可选) +│ ├── en.json +│ └── zh-CN.json +│ +├── scripts/ # 构建/工具脚本 +│ ├── build.ts # 构建脚本 +│ ├── release.ts # 发布脚本 +│ ├── notarize.ts # macOS 公证 +│ └── dev.ts # 开发脚本 +│ +├── tests/ # 测试 +│ ├── unit/ # 单元测试 +│ ├── integration/ # 集成测试 +│ └── e2e/ # E2E 测试 +│ ├── setup.spec.ts # 安装向导测试 +│ ├── chat.spec.ts # 聊天功能测试 +│ └── channels.spec.ts # 通道配置测试 +│ +├── .env.example # 环境变量示例 +├── .eslintrc.cjs # ESLint 配置 +├── .prettierrc # Prettier 配置 +├── electron-builder.yml # 打包配置 +├── package.json +├── tsconfig.json # TypeScript 配置 (主) +├── tsconfig.node.json # TypeScript 配置 (Node) +├── vite.config.ts # Vite 配置 +├── tailwind.config.js # Tailwind 配置 +├── CHANGELOG.md +├── LICENSE +└── README.md +``` + +### 2.4 核心模块设计 + +#### 2.4.1 Gateway 管理器 + +```typescript +// electron/gateway/manager.ts +import { spawn, ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; +import WebSocket from 'ws'; + +export interface GatewayStatus { + state: 'stopped' | 'starting' | 'running' | 'error'; + port: number; + pid?: number; + uptime?: number; + error?: string; +} + +export class GatewayManager extends EventEmitter { + private process: ChildProcess | null = null; + private ws: WebSocket | null = null; + private status: GatewayStatus = { state: 'stopped', port: 18789 }; + + // 启动 Gateway + async start(): Promise { + if (this.status.state === 'running') return; + + this.setStatus({ state: 'starting' }); + + try { + // 检查已有进程 + const existing = await this.findExisting(); + if (existing) { + await this.connect(existing.port); + return; + } + + // 启动新进程 + this.process = spawn('openclaw', [ + 'gateway', 'run', + '--port', String(this.status.port), + '--force' + ], { + stdio: ['ignore', 'pipe', 'pipe'], + detached: true, + }); + + this.process.on('exit', (code) => { + this.setStatus({ state: 'stopped' }); + this.emit('exit', code); + }); + + // 等待就绪并连接 + await this.waitForReady(); + await this.connect(this.status.port); + + } catch (error) { + this.setStatus({ state: 'error', error: String(error) }); + throw error; + } + } + + // 停止 Gateway + async stop(): Promise { + this.ws?.close(); + this.process?.kill(); + this.setStatus({ state: 'stopped' }); + } + + // RPC 调用 + async rpc(method: string, params?: unknown): Promise { + return new Promise((resolve, reject) => { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + reject(new Error('Gateway not connected')); + return; + } + + const id = crypto.randomUUID(); + const handler = (data: WebSocket.Data) => { + const msg = JSON.parse(data.toString()); + if (msg.id === id) { + this.ws?.off('message', handler); + if (msg.error) reject(msg.error); + else resolve(msg.result as T); + } + }; + + this.ws.on('message', handler); + this.ws.send(JSON.stringify({ jsonrpc: '2.0', id, method, params })); + }); + } + + private setStatus(update: Partial): void { + this.status = { ...this.status, ...update }; + this.emit('status', this.status); + } +} +``` + +#### 2.4.2 安装向导流程 + +```typescript +// src/pages/Setup/index.tsx +import { useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; + +interface SetupStep { + id: string; + title: string; + description: string; + component: React.ComponentType; +} + +const steps: SetupStep[] = [ + { + id: 'welcome', + title: '欢迎使用 ClawX', + description: '您的 AI 助手,即将启程', + component: WelcomeStep, + }, + { + id: 'runtime', + title: '环境检测', + description: '检测并安装必要运行环境', + component: RuntimeStep, + }, + { + id: 'provider', + title: '选择 AI 模型', + description: '配置您的 AI 服务提供商', + component: ProviderStep, + }, + { + id: 'channel', + title: '连接消息应用', + description: '绑定 WhatsApp、Telegram 等', + component: ChannelStep, + }, + { + id: 'skills', + title: '选择技能包', + description: '挑选预装技能,稍后可调整', + component: SkillStep, + }, + { + id: 'complete', + title: '设置完成', + description: '一切就绪,开始使用吧!', + component: CompleteStep, + }, +]; + +export function SetupWizard() { + const [currentStep, setCurrentStep] = useState(0); + const [setupData, setSetupData] = useState({}); + + const step = steps[currentStep]; + const StepComponent = step.component; + + const handleNext = (data: Partial) => { + setSetupData({ ...setupData, ...data }); + setCurrentStep((i) => Math.min(i + 1, steps.length - 1)); + }; + + return ( +
+ {/* 进度指示器 */} +
+ {steps.map((s, i) => ( +
+
+ {i < steps.length - 1 && ( +
+ )} +
+ ))} +
+ + {/* 步骤内容 */} + + +

{step.title}

+

{step.description}

+ + setCurrentStep((i) => Math.max(i - 1, 0))} + /> +
+
+
+ ); +} +``` + +#### 2.4.3 预装技能包定义 + +```typescript +// resources/skills/bundles.ts +export interface SkillBundle { + id: string; + name: string; + nameZh: string; + description: string; + descriptionZh: string; + icon: string; + skills: string[]; + recommended?: boolean; +} + +export const skillBundles: SkillBundle[] = [ + { + id: 'productivity', + name: 'Productivity', + nameZh: '效率办公', + description: 'Calendar, reminders, notes, email management', + descriptionZh: '日历、提醒、笔记、邮件管理', + icon: '📋', + skills: [ + 'apple-reminders', + 'apple-notes', + 'himalaya', + 'notion', + 'obsidian', + 'trello', + ], + recommended: true, + }, + { + id: 'developer', + name: 'Developer Tools', + nameZh: '开发者工具', + description: 'GitHub, coding assistant, terminal management', + descriptionZh: 'GitHub、代码助手、终端管理', + icon: '💻', + skills: [ + 'github', + 'coding-agent', + 'tmux', + ], + recommended: true, + }, + { + id: 'smart-home', + name: 'Smart Home', + nameZh: '智能家居', + description: 'Lights, music, device control', + descriptionZh: '灯光、音乐、设备控制', + icon: '🏠', + skills: [ + 'openhue', + 'sonoscli', + 'spotify-player', + ], + }, + { + id: 'media', + name: 'Media & Creative', + nameZh: '多媒体创作', + description: 'Image generation, video processing, audio transcription', + descriptionZh: '图片生成、视频处理、音频转写', + icon: '🎨', + skills: [ + 'openai-image-gen', + 'nano-banana-pro', + 'video-frames', + 'openai-whisper-api', + ], + }, + { + id: 'communication', + name: 'Communication', + nameZh: '通讯增强', + description: 'Messaging, voice calls, notifications', + descriptionZh: '消息管理、语音通话、通知', + icon: '💬', + skills: [ + 'discord', + 'slack', + 'voice-call', + 'imsg', + ], + }, + { + id: 'security', + name: 'Security & Privacy', + nameZh: '安全隐私', + description: 'Password management, secrets', + descriptionZh: '密码管理、密钥存储', + icon: '🔐', + skills: [ + '1password', + ], + }, + { + id: 'information', + name: 'Information', + nameZh: '信息获取', + description: 'Weather, news, web browsing', + descriptionZh: '天气、新闻、网页浏览', + icon: '🌐', + skills: [ + 'weather', + 'blogwatcher', + 'summarize', + ], + }, +]; +``` + +### 2.5 默认预装机制设计 + +ClawX 需要在代码层面实现**默认技能**和**默认扩展**的预装机制,确保用户开箱即用。 + +#### 2.5.1 预装配置定义 + +```typescript +// electron/presets/defaults.ts + +/** + * ClawX 预装配置 + * 定义首次安装时默认启用的技能和扩展 + */ +export interface ClawXPresets { + /** 默认启用的技能 ID 列表 */ + skills: string[]; + + /** 默认启用的扩展 ID 列表 */ + extensions: string[]; + + /** 默认技能包 (用户可在安装向导中选择) */ + defaultBundles: string[]; + + /** 核心技能 (始终启用,用户不可禁用) */ + coreSkills: string[]; + + /** 核心扩展 (始终启用,用户不可禁用) */ + coreExtensions: string[]; +} + +/** + * ClawX 默认预装配置 + */ +export const CLAWX_PRESETS: ClawXPresets = { + // 默认启用的技能 (首次安装自动启用) + skills: [ + // Tier 1: 核心体验技能 + 'coding-agent', // 代码助手 (类似 opencode) + 'canvas', // Canvas UI + 'summarize', // 内容摘要 + + // Tier 2: 常用工具技能 + 'weather', // 天气查询 + 'github', // GitHub 集成 + 'clawhub', // 技能市场 + ], + + // 默认启用的扩展 + extensions: [ + 'lobster', // UI 美化 + 'memory-core', // 记忆系统 + ], + + // 默认推荐的技能包 (安装向导中预选) + defaultBundles: [ + 'productivity', + 'developer', + ], + + // 核心技能 (不可禁用) + coreSkills: [ + 'coding-agent', // 代码能力是核心体验 + ], + + // 核心扩展 (不可禁用) + coreExtensions: [ + 'memory-core', // 记忆是核心功能 + ], +}; +``` + +#### 2.5.2 预装加载器 + +```typescript +// electron/installer/preset-loader.ts + +import { CLAWX_PRESETS } from '../presets/defaults'; +import { GatewayManager } from '../gateway/manager'; + +export interface PresetLoadResult { + skills: { id: string; status: 'loaded' | 'failed'; error?: string }[]; + extensions: { id: string; status: 'loaded' | 'failed'; error?: string }[]; +} + +export class PresetLoader { + constructor(private gateway: GatewayManager) {} + + /** + * 首次安装时加载所有预装项 + */ + async loadInitialPresets(): Promise { + const result: PresetLoadResult = { skills: [], extensions: [] }; + + // 1. 加载核心扩展 (优先级最高) + for (const extId of CLAWX_PRESETS.coreExtensions) { + const status = await this.loadExtension(extId, { required: true }); + result.extensions.push({ id: extId, ...status }); + } + + // 2. 加载默认扩展 + for (const extId of CLAWX_PRESETS.extensions) { + if (!CLAWX_PRESETS.coreExtensions.includes(extId)) { + const status = await this.loadExtension(extId, { required: false }); + result.extensions.push({ id: extId, ...status }); + } + } + + // 3. 加载核心技能 + for (const skillId of CLAWX_PRESETS.coreSkills) { + const status = await this.loadSkill(skillId, { required: true }); + result.skills.push({ id: skillId, ...status }); + } + + // 4. 加载默认技能 + for (const skillId of CLAWX_PRESETS.skills) { + if (!CLAWX_PRESETS.coreSkills.includes(skillId)) { + const status = await this.loadSkill(skillId, { required: false }); + result.skills.push({ id: skillId, ...status }); + } + } + + return result; + } + + /** + * 加载用户选择的技能包 + */ + async loadBundles(bundleIds: string[]): Promise { + const { skillBundles } = await import('../../resources/skills/bundles'); + + for (const bundleId of bundleIds) { + const bundle = skillBundles.find(b => b.id === bundleId); + if (bundle) { + for (const skillId of bundle.skills) { + await this.loadSkill(skillId, { required: false }); + } + } + } + } + + private async loadSkill( + skillId: string, + opts: { required: boolean } + ): Promise<{ status: 'loaded' | 'failed'; error?: string }> { + try { + // 通过 Gateway RPC 启用技能 + await this.gateway.rpc('skills.enable', { skillId }); + return { status: 'loaded' }; + } catch (error) { + if (opts.required) { + throw new Error(`Failed to load required skill: ${skillId}`); + } + return { status: 'failed', error: String(error) }; + } + } + + private async loadExtension( + extId: string, + opts: { required: boolean } + ): Promise<{ status: 'loaded' | 'failed'; error?: string }> { + try { + // 通过 Gateway RPC 启用扩展 + await this.gateway.rpc('plugins.enable', { pluginId: extId }); + return { status: 'loaded' }; + } catch (error) { + if (opts.required) { + throw new Error(`Failed to load required extension: ${extId}`); + } + return { status: 'failed', error: String(error) }; + } + } +} +``` + +#### 2.5.3 预装状态管理 + +```typescript +// src/stores/presets.ts + +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +interface PresetState { + /** 是否已完成首次预装 */ + initialized: boolean; + + /** 用户选择的技能包 */ + selectedBundles: string[]; + + /** 用户额外启用的技能 */ + enabledSkills: string[]; + + /** 用户禁用的默认技能 (不包括核心技能) */ + disabledSkills: string[]; + + /** 用户额外启用的扩展 */ + enabledExtensions: string[]; + + /** 用户禁用的默认扩展 (不包括核心扩展) */ + disabledExtensions: string[]; + + // Actions + setInitialized: (value: boolean) => void; + setSelectedBundles: (bundles: string[]) => void; + toggleSkill: (skillId: string, enabled: boolean) => void; + toggleExtension: (extId: string, enabled: boolean) => void; + + // Computed + getEffectiveSkills: () => string[]; + getEffectiveExtensions: () => string[]; +} + +export const usePresetStore = create()( + persist( + (set, get) => ({ + initialized: false, + selectedBundles: [], + enabledSkills: [], + disabledSkills: [], + enabledExtensions: [], + disabledExtensions: [], + + setInitialized: (value) => set({ initialized: value }), + + setSelectedBundles: (bundles) => set({ selectedBundles: bundles }), + + toggleSkill: (skillId, enabled) => { + const { enabledSkills, disabledSkills } = get(); + if (enabled) { + set({ + enabledSkills: [...enabledSkills, skillId], + disabledSkills: disabledSkills.filter(id => id !== skillId), + }); + } else { + set({ + enabledSkills: enabledSkills.filter(id => id !== skillId), + disabledSkills: [...disabledSkills, skillId], + }); + } + }, + + toggleExtension: (extId, enabled) => { + const { enabledExtensions, disabledExtensions } = get(); + if (enabled) { + set({ + enabledExtensions: [...enabledExtensions, extId], + disabledExtensions: disabledExtensions.filter(id => id !== extId), + }); + } else { + set({ + enabledExtensions: enabledExtensions.filter(id => id !== extId), + disabledExtensions: [...disabledExtensions, extId], + }); + } + }, + + // 计算实际生效的技能列表 + getEffectiveSkills: () => { + const { selectedBundles, enabledSkills, disabledSkills } = get(); + const { CLAWX_PRESETS } = require('../../electron/presets/defaults'); + const { skillBundles } = require('../../resources/skills/bundles'); + + // 1. 核心技能 (始终包含) + const skills = new Set(CLAWX_PRESETS.coreSkills); + + // 2. 默认技能 + CLAWX_PRESETS.skills.forEach((id: string) => skills.add(id)); + + // 3. 选中的技能包 + for (const bundleId of selectedBundles) { + const bundle = skillBundles.find((b: any) => b.id === bundleId); + bundle?.skills.forEach((id: string) => skills.add(id)); + } + + // 4. 用户额外启用的 + enabledSkills.forEach(id => skills.add(id)); + + // 5. 移除用户禁用的 (但不能移除核心技能) + disabledSkills.forEach(id => { + if (!CLAWX_PRESETS.coreSkills.includes(id)) { + skills.delete(id); + } + }); + + return Array.from(skills); + }, + + getEffectiveExtensions: () => { + // 类似逻辑... + const { enabledExtensions, disabledExtensions } = get(); + const { CLAWX_PRESETS } = require('../../electron/presets/defaults'); + + const extensions = new Set(CLAWX_PRESETS.coreExtensions); + CLAWX_PRESETS.extensions.forEach((id: string) => extensions.add(id)); + enabledExtensions.forEach(id => extensions.add(id)); + disabledExtensions.forEach(id => { + if (!CLAWX_PRESETS.coreExtensions.includes(id)) { + extensions.delete(id); + } + }); + + return Array.from(extensions); + }, + }), + { + name: 'clawx-presets', + } + ) +); +``` + +#### 2.5.4 安装向导集成 + +```typescript +// src/pages/Setup/SkillStep.tsx + +import { usePresetStore } from '@/stores/presets'; +import { skillBundles } from '@/resources/skills/bundles'; +import { CLAWX_PRESETS } from '@electron/presets/defaults'; + +export function SkillStep({ onNext, onBack }: StepProps) { + const { selectedBundles, setSelectedBundles } = usePresetStore(); + + // 默认预选推荐的技能包 + const [selected, setSelected] = useState( + selectedBundles.length > 0 + ? selectedBundles + : CLAWX_PRESETS.defaultBundles + ); + + const handleToggle = (bundleId: string) => { + setSelected(prev => + prev.includes(bundleId) + ? prev.filter(id => id !== bundleId) + : [...prev, bundleId] + ); + }; + + const handleNext = () => { + setSelectedBundles(selected); + onNext({ bundles: selected }); + }; + + return ( +
+
+ {skillBundles.map(bundle => ( + handleToggle(bundle.id)} + > + +
+ {bundle.icon} +
+ {bundle.nameZh} + {bundle.descriptionZh} +
+
+
+ +
+ {bundle.skills.slice(0, 4).map(skill => ( + + {skill} + + ))} + {bundle.skills.length > 4 && ( + + +{bundle.skills.length - 4} + + )} +
+
+ {bundle.recommended && ( +
+ 推荐 +
+ )} +
+ ))} +
+ + {/* 核心技能提示 */} + + + + 以下核心技能将始终启用: + {CLAWX_PRESETS.coreSkills.map(id => ( + {id} + ))} + + + +
+ + +
+
+ ); +} +``` + +#### 2.5.5 预装层级与优先级 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ClawX 预装层级架构 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Layer 0: 核心层 (Core) - 不可禁用 │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Skills: coding-agent │ │ +│ │ Extensions: memory-core │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ │ 始终启用 │ +│ │ +│ Layer 1: 默认层 (Default) - 可禁用 │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Skills: canvas, summarize, weather, github, clawhub │ │ +│ │ Extensions: lobster │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ │ 首次安装自动启用 │ +│ │ +│ Layer 2: 技能包层 (Bundle) - 用户选择 │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ productivity: apple-reminders, notion, obsidian... │ │ +│ │ developer: github, coding-agent, tmux... │ │ +│ │ smart-home: openhue, sonoscli, spotify-player... │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ ▲ │ +│ │ 安装向导中选择 │ +│ │ +│ Layer 3: 用户层 (User) - 完全自定义 │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 用户手动启用/禁用的技能和扩展 │ │ +│ │ (通过设置页面或技能市场) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +优先级: Layer 3 > Layer 2 > Layer 1 > Layer 0 (不可覆盖) +``` + +#### 2.5.6 配置文件结构 + +```json5 +// ~/.clawx/presets.json (用户本地配置) +{ + "version": 1, + "initialized": true, + "initializedAt": "2026-02-05T12:00:00Z", + + // 用户选择的技能包 + "bundles": ["productivity", "developer"], + + // 用户自定义覆盖 + "overrides": { + "skills": { + "enabled": ["custom-skill-1"], // 额外启用 + "disabled": ["weather"] // 禁用默认 + }, + "extensions": { + "enabled": ["custom-ext-1"], + "disabled": [] + } + }, + + // 同步到 OpenClaw 的配置 (生成后写入 ~/.openclaw/config.json) + "syncedConfig": { + "skills": { + "enabled": ["coding-agent", "canvas", "github", "notion", "custom-skill-1"] + }, + "plugins": { + "entries": { + "memory-core": { "enabled": true }, + "lobster": { "enabled": true } + } + } + } +} +``` + +### 2.6 跨平台自动更新系统 + +ClawX 需要实现**主动式**自动更新机制,在新版本发布后主动通知用户,同时支持用户手动检查更新。 + +#### 2.6.1 更新策略设计 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ClawX 自动更新流程 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 启动检查 │ │ 定时检查 │ │ 手动检查 │ │ +│ │ (App Start) │ │ (每6小时) │ │ (用户触发) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └───────────────────┼───────────────────┘ │ +│ ▼ │ +│ ┌─────────────────────┐ │ +│ │ Version Checker │ │ +│ │ 检查 GitHub/CDN │ │ +│ └──────────┬──────────┘ │ +│ │ │ +│ ┌──────────────┴──────────────┐ │ +│ ▼ ▼ │ +│ ┌────────────────┐ ┌────────────────┐ │ +│ │ 无新版本 │ │ 有新版本 │ │ +│ │ 静默结束 │ │ │ │ +│ └────────────────┘ └───────┬────────┘ │ +│ │ │ +│ ┌───────────┴───────────┐ │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ 主动通知弹窗 │ │ 静默下载 │ │ +│ │ (询问用户) │ │ (后台进行) │ │ +│ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ +│ ┌────────────┴────────────┐ │ │ +│ ▼ ▼ │ │ +│ ┌────────────────┐ ┌────────────────┐ │ │ +│ │ 立即更新 │ │ 稍后提醒 │ │ │ +│ │ (下载安装) │ │ (记录时间) │ │ │ +│ └───────┬────────┘ └────────────────┘ │ │ +│ │ │ │ +│ └────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────┐ │ +│ │ 下载完成通知 │ │ +│ │ "重启以完成更新" │ │ +│ └─────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### 2.6.2 更新管理器实现 + +```typescript +// electron/updater/index.ts + +import { autoUpdater, UpdateInfo } from 'electron-updater'; +import { app, BrowserWindow, dialog, Notification } from 'electron'; +import { EventEmitter } from 'events'; +import log from 'electron-log'; + +export interface UpdateConfig { + /** 更新通道: stable | beta | dev */ + channel: 'stable' | 'beta' | 'dev'; + + /** 是否自动下载 */ + autoDownload: boolean; + + /** 是否允许降级 */ + allowDowngrade: boolean; + + /** 检查间隔 (毫秒) */ + checkInterval: number; + + /** 是否显示主动通知 */ + showNotification: boolean; +} + +export interface UpdateStatus { + state: 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'error'; + currentVersion: string; + latestVersion?: string; + releaseNotes?: string; + downloadProgress?: number; + error?: string; +} + +const DEFAULT_CONFIG: UpdateConfig = { + channel: 'stable', + autoDownload: false, // 默认不自动下载,先询问用户 + allowDowngrade: false, + checkInterval: 6 * 60 * 60 * 1000, // 6小时 + showNotification: true, +}; + +export class UpdateManager extends EventEmitter { + private config: UpdateConfig; + private status: UpdateStatus; + private checkTimer: NodeJS.Timeout | null = null; + private mainWindow: BrowserWindow | null = null; + + constructor(config: Partial = {}) { + super(); + this.config = { ...DEFAULT_CONFIG, ...config }; + this.status = { + state: 'idle', + currentVersion: app.getVersion(), + }; + + this.setupAutoUpdater(); + } + + private setupAutoUpdater(): void { + // 配置 electron-updater + autoUpdater.logger = log; + autoUpdater.autoDownload = this.config.autoDownload; + autoUpdater.allowDowngrade = this.config.allowDowngrade; + + // 设置更新通道 + autoUpdater.channel = this.config.channel; + + // GitHub Releases 作为更新源 + autoUpdater.setFeedURL({ + provider: 'github', + owner: 'clawx', + repo: 'clawx', + }); + + // 事件监听 + autoUpdater.on('checking-for-update', () => { + this.setStatus({ state: 'checking' }); + }); + + autoUpdater.on('update-available', (info: UpdateInfo) => { + this.setStatus({ + state: 'available', + latestVersion: info.version, + releaseNotes: this.formatReleaseNotes(info.releaseNotes), + }); + + // 主动通知用户 + if (this.config.showNotification) { + this.showUpdateNotification(info); + } + }); + + autoUpdater.on('update-not-available', () => { + this.setStatus({ state: 'idle' }); + }); + + autoUpdater.on('download-progress', (progress) => { + this.setStatus({ + state: 'downloading', + downloadProgress: Math.round(progress.percent), + }); + }); + + autoUpdater.on('update-downloaded', (info: UpdateInfo) => { + this.setStatus({ state: 'downloaded' }); + this.showDownloadedNotification(info); + }); + + autoUpdater.on('error', (error) => { + this.setStatus({ state: 'error', error: error.message }); + }); + } + + /** + * 初始化更新器 (应用启动时调用) + */ + initialize(mainWindow: BrowserWindow): void { + this.mainWindow = mainWindow; + + // 启动时检查更新 (延迟5秒,避免影响启动速度) + setTimeout(() => this.checkForUpdates(true), 5000); + + // 定时检查 + this.startPeriodicCheck(); + } + + /** + * 启动定时检查 + */ + private startPeriodicCheck(): void { + if (this.checkTimer) { + clearInterval(this.checkTimer); + } + + this.checkTimer = setInterval(() => { + this.checkForUpdates(true); + }, this.config.checkInterval); + } + + /** + * 检查更新 + * @param silent 是否静默 (不显示"已是最新版本"提示) + */ + async checkForUpdates(silent: boolean = false): Promise { + try { + const result = await autoUpdater.checkForUpdates(); + + if (!result?.updateInfo && !silent) { + // 手动检查且无更新时,显示提示 + this.showNoUpdateDialog(); + } + } catch (error) { + if (!silent) { + this.showErrorDialog(error as Error); + } + } + } + + /** + * 下载更新 + */ + async downloadUpdate(): Promise { + if (this.status.state !== 'available') return; + await autoUpdater.downloadUpdate(); + } + + /** + * 安装更新并重启 + */ + quitAndInstall(): void { + autoUpdater.quitAndInstall(false, true); + } + + /** + * 显示更新可用通知 + */ + private showUpdateNotification(info: UpdateInfo): void { + // 系统通知 + if (Notification.isSupported()) { + const notification = new Notification({ + title: 'ClawX 有新版本可用', + body: `版本 ${info.version} 已发布,点击查看详情`, + icon: app.isPackaged + ? undefined + : 'resources/icons/icon.png', + }); + + notification.on('click', () => { + this.showUpdateDialog(info); + }); + + notification.show(); + } + + // 同时发送到渲染进程 + this.mainWindow?.webContents.send('update:available', { + version: info.version, + releaseNotes: this.formatReleaseNotes(info.releaseNotes), + }); + } + + /** + * 显示更新对话框 (主动询问用户) + */ + private async showUpdateDialog(info: UpdateInfo): Promise { + const releaseNotes = this.formatReleaseNotes(info.releaseNotes); + + const result = await dialog.showMessageBox(this.mainWindow!, { + type: 'info', + title: '发现新版本', + message: `ClawX ${info.version} 已发布`, + detail: `当前版本: ${this.status.currentVersion}\n\n更新内容:\n${releaseNotes}`, + buttons: ['立即更新', '稍后提醒', '跳过此版本'], + defaultId: 0, + cancelId: 1, + }); + + switch (result.response) { + case 0: // 立即更新 + this.downloadUpdate(); + break; + case 1: // 稍后提醒 + // 30分钟后再次提醒 + setTimeout(() => this.showUpdateNotification(info), 30 * 60 * 1000); + break; + case 2: // 跳过此版本 + this.skipVersion(info.version); + break; + } + } + + /** + * 显示下载完成通知 + */ + private showDownloadedNotification(info: UpdateInfo): void { + if (Notification.isSupported()) { + const notification = new Notification({ + title: '更新已就绪', + body: `ClawX ${info.version} 已下载完成,点击重启应用以完成更新`, + }); + + notification.on('click', () => { + this.showRestartDialog(); + }); + + notification.show(); + } + + this.mainWindow?.webContents.send('update:downloaded', { + version: info.version, + }); + } + + /** + * 显示重启对话框 + */ + private async showRestartDialog(): Promise { + const result = await dialog.showMessageBox(this.mainWindow!, { + type: 'info', + title: '更新已就绪', + message: '重启应用以完成更新', + detail: '更新已下载完成,是否立即重启应用?', + buttons: ['立即重启', '稍后'], + defaultId: 0, + }); + + if (result.response === 0) { + this.quitAndInstall(); + } + } + + /** + * 显示无更新对话框 (手动检查时) + */ + private showNoUpdateDialog(): void { + dialog.showMessageBox(this.mainWindow!, { + type: 'info', + title: '检查更新', + message: '已是最新版本', + detail: `当前版本 ${this.status.currentVersion} 已是最新`, + buttons: ['确定'], + }); + } + + private showErrorDialog(error: Error): void { + dialog.showMessageBox(this.mainWindow!, { + type: 'error', + title: '更新检查失败', + message: '无法检查更新', + detail: error.message, + buttons: ['确定'], + }); + } + + private formatReleaseNotes(notes: string | any): string { + if (typeof notes === 'string') return notes; + if (Array.isArray(notes)) { + return notes.map(n => n.note || n).join('\n'); + } + return ''; + } + + private skipVersion(version: string): void { + // 存储跳过的版本 + const store = require('electron-store'); + const settings = new store(); + const skipped = settings.get('update.skippedVersions', []) as string[]; + if (!skipped.includes(version)) { + settings.set('update.skippedVersions', [...skipped, version]); + } + } + + private setStatus(update: Partial): void { + this.status = { ...this.status, ...update }; + this.emit('status', this.status); + this.mainWindow?.webContents.send('update:status', this.status); + } + + getStatus(): UpdateStatus { + return this.status; + } + + setConfig(config: Partial): void { + this.config = { ...this.config, ...config }; + autoUpdater.channel = this.config.channel; + autoUpdater.autoDownload = this.config.autoDownload; + + // 重启定时检查 + this.startPeriodicCheck(); + } +} + +// 导出单例 +export const updateManager = new UpdateManager(); +``` + +#### 2.6.3 渲染进程更新 UI + +```typescript +// src/hooks/useUpdate.ts + +import { useState, useEffect } from 'react'; + +interface UpdateStatus { + state: 'idle' | 'checking' | 'available' | 'downloading' | 'downloaded' | 'error'; + currentVersion: string; + latestVersion?: string; + releaseNotes?: string; + downloadProgress?: number; + error?: string; +} + +export function useUpdate() { + const [status, setStatus] = useState({ + state: 'idle', + currentVersion: '0.0.0', + }); + + useEffect(() => { + // 监听主进程更新状态 + const handleStatus = (_: any, newStatus: UpdateStatus) => { + setStatus(newStatus); + }; + + window.electron.ipcRenderer.on('update:status', handleStatus); + + // 获取初始状态 + window.electron.ipcRenderer.invoke('update:getStatus').then(setStatus); + + return () => { + window.electron.ipcRenderer.off('update:status', handleStatus); + }; + }, []); + + const checkForUpdates = () => { + window.electron.ipcRenderer.invoke('update:check'); + }; + + const downloadUpdate = () => { + window.electron.ipcRenderer.invoke('update:download'); + }; + + const installUpdate = () => { + window.electron.ipcRenderer.invoke('update:install'); + }; + + return { + status, + checkForUpdates, + downloadUpdate, + installUpdate, + }; +} +``` + +```typescript +// src/components/UpdateNotification.tsx + +import { useUpdate } from '@/hooks/useUpdate'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Download, RefreshCw, X, Loader2 } from 'lucide-react'; + +export function UpdateNotification() { + const { status, downloadUpdate, installUpdate } = useUpdate(); + const [dismissed, setDismissed] = useState(false); + + // 只在有更新可用或已下载时显示 + const shouldShow = + !dismissed && + (status.state === 'available' || status.state === 'downloaded'); + + return ( + + {shouldShow && ( + + + +
+ + {status.state === 'available' ? ( + <> + + 新版本可用 + + ) : ( + <> + + 更新已就绪 + + )} + + +
+
+ +

+ ClawX {status.latestVersion} 已发布 +

+ + {status.state === 'downloading' && ( +
+ +

+ 下载中 {status.downloadProgress}% +

+
+ )} + +
+ {status.state === 'available' && ( + + )} + {status.state === 'downloaded' && ( + + )} + +
+
+
+
+ )} +
+ ); +} +``` + +#### 2.6.4 设置页面手动检查入口 + +```typescript +// src/pages/Settings/GeneralSettings.tsx + +import { useUpdate } from '@/hooks/useUpdate'; + +export function GeneralSettings() { + const { status, checkForUpdates } = useUpdate(); + + return ( + + + 通用设置 + + + {/* 版本与更新 */} +
+ + +
+
+

ClawX

+

+ 当前版本: {status.currentVersion} +

+ {status.latestVersion && status.state === 'available' && ( +

+ 新版本可用: {status.latestVersion} +

+ )} +
+ +
+ + {/* 更新设置 */} +
+
+
+

自动检查更新

+

+ 启动时和每6小时自动检查 +

+
+ +
+ +
+
+

自动下载更新

+

+ 发现新版本后在后台自动下载 +

+
+ +
+ +
+
+

更新通道

+

+ 选择接收更新的类型 +

+
+ +
+
+
+ + + + {/* 其他设置... */} +
+
+ ); +} +``` + +#### 2.6.5 跨平台配置 + +```typescript +// electron/updater/channels.ts + +import { Platform } from 'electron-builder'; + +export interface PlatformUpdateConfig { + /** 更新源 URL */ + feedUrl: string; + + /** 是否支持差量更新 */ + supportsDelta: boolean; + + /** 安装方式 */ + installMethod: 'nsis' | 'dmg' | 'appimage' | 'deb' | 'rpm'; +} + +export const PLATFORM_CONFIGS: Record = { + darwin: { + feedUrl: 'https://releases.clawx.app/mac', + supportsDelta: true, // macOS 支持 Sparkle 差量更新 + installMethod: 'dmg', + }, + win32: { + feedUrl: 'https://releases.clawx.app/win', + supportsDelta: true, // Windows NSIS 支持差量 + installMethod: 'nsis', + }, + linux: { + feedUrl: 'https://releases.clawx.app/linux', + supportsDelta: false, // AppImage 不支持差量 + installMethod: 'appimage', + }, +} as Record; + +// electron-builder.yml 配置示例 +export const ELECTRON_BUILDER_CONFIG = ` +appId: app.clawx.desktop +productName: ClawX +copyright: Copyright © 2026 ClawX + +publish: + - provider: github + owner: clawx + repo: clawx + releaseType: release + +mac: + category: public.app-category.productivity + target: + - target: dmg + arch: [universal] + - target: zip + arch: [universal] + notarize: + teamId: \${env.APPLE_TEAM_ID} + +win: + target: + - target: nsis + arch: [x64, arm64] + publisherName: ClawX Inc. + +nsis: + oneClick: false + allowToChangeInstallationDirectory: true + differentialPackage: true # 启用差量更新 + +linux: + target: + - target: AppImage + arch: [x64, arm64] + - target: deb + arch: [x64] + category: Utility +`; +``` + +#### 2.6.6 更新时间线与用户交互 + +``` +用户视角的更新流程: + +┌─────────────────────────────────────────────────────────────────┐ +│ 场景 A: 有新版本时主动通知 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. 用户正在使用 ClawX │ +│ │ │ +│ 2. 系统检测到新版本 (启动时/定时检查) │ +│ │ │ +│ 3. 右上角弹出通知卡片 ────────────────────┐ │ +│ │ │ │ +│ │ ┌─────────────────────────────┐ │ │ +│ │ │ 🔔 新版本可用 │ │ │ +│ │ │ ClawX 1.2.0 已发布 │ │ │ +│ │ │ [立即下载] [稍后] │ │ │ +│ │ └─────────────────────────────┘ │ │ +│ │ │ │ +│ 4. 用户点击"立即下载" │ +│ │ │ +│ 5. 后台下载,通知卡片显示进度 │ +│ │ │ +│ 6. 下载完成,通知"重启以完成更新" │ +│ │ │ +│ 7. 用户选择"立即重启"或继续工作后重启 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ 场景 B: 用户手动检查更新 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. 用户打开 设置 → 通用 → 检查更新 │ +│ │ │ +│ 2. 显示"检查中..." │ +│ │ │ +│ 3a. 无更新 → 弹窗提示"已是最新版本 (1.1.0)" │ +│ │ │ +│ 3b. 有更新 → 弹窗显示更新详情 │ +│ │ │ +│ │ ┌─────────────────────────────────────┐ │ +│ │ │ 发现新版本 │ │ +│ │ │ │ │ +│ │ │ ClawX 1.2.0 已发布 │ │ +│ │ │ 当前版本: 1.1.0 │ │ +│ │ │ │ │ +│ │ │ 更新内容: │ │ +│ │ │ • 新增技能市场 │ │ +│ │ │ • 修复 Windows 启动问题 │ │ +│ │ │ • 性能优化 │ │ +│ │ │ │ │ +│ │ │ [立即更新] [稍后提醒] [跳过此版本] │ │ +│ │ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 2.7 GUI 权限提升机制 + +ClawX 需要在某些操作时获取管理员权限(如全局安装 Node.js、修改系统 PATH),必须通过**图形化密码弹窗**而非命令行 `sudo`。 + +#### 2.7.1 需要管理员权限的场景 + +| 场景 | 操作 | 原因 | +|------|------|------| +| Node.js 安装 | 写入 `/usr/local/bin` (macOS/Linux) | 系统目录需要 root | +| 全局 npm 安装 | `npm install -g openclaw` | 系统 node_modules | +| PATH 配置 | 修改 `/etc/paths.d/` | 系统级环境变量 | +| 端口 < 1024 | Gateway 使用低端口 | 特权端口 | +| 系统服务注册 | LaunchDaemon / systemd | 开机自启 | + +#### 2.7.2 跨平台权限提升架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ClawX 权限提升流程 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────┐ │ +│ │ 需要管理员权限 │ │ +│ │ 的操作触发 │ │ +│ └────────┬─────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────┐ │ +│ │ PrivilegeManager │ │ +│ │ 检测当前平台 │ │ +│ └────────┬─────────┘ │ +│ │ │ +│ ┌─────┴─────┬─────────────┐ │ +│ ▼ ▼ ▼ │ +│ ┌──────┐ ┌──────┐ ┌──────┐ │ +│ │macOS │ │Windows│ │Linux │ │ +│ └──┬───┘ └──┬───┘ └──┬───┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │osascript│ │ UAC │ │pkexec │ │ +│ │密码弹窗 │ │提升弹窗│ │密码弹窗│ │ +│ └────────┘ └────────┘ └────────┘ │ +│ │ │ │ │ +│ └──────────┴───────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────┐ │ +│ │ 以管理员身份执行命令 │ │ +│ │ (无需终端 sudo 输入) │ │ +│ └──────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### 2.7.3 权限管理器实现 + +```typescript +// electron/privilege/index.ts + +import { dialog } from 'electron'; +import { platform } from 'os'; +import { DarwinAdmin } from './darwin-admin'; +import { Win32Admin } from './win32-admin'; +import { LinuxAdmin } from './linux-admin'; + +export interface PrivilegeResult { + success: boolean; + stdout?: string; + stderr?: string; + error?: string; +} + +export interface PrivilegeOptions { + /** 向用户展示的操作说明 */ + reason: string; + + /** 图标路径 */ + icon?: string; + + /** 超时时间 (毫秒) */ + timeout?: number; +} + +/** + * 跨平台权限提升管理器 + * 通过 GUI 弹窗获取管理员权限,而非命令行 sudo + */ +export class PrivilegeManager { + private admin: DarwinAdmin | Win32Admin | LinuxAdmin; + + constructor() { + switch (platform()) { + case 'darwin': + this.admin = new DarwinAdmin(); + break; + case 'win32': + this.admin = new Win32Admin(); + break; + case 'linux': + this.admin = new LinuxAdmin(); + break; + default: + throw new Error(`Unsupported platform: ${platform()}`); + } + } + + /** + * 检查是否已有管理员权限 + */ + async hasAdminPrivilege(): Promise { + return this.admin.hasAdminPrivilege(); + } + + /** + * 以管理员权限执行命令 (会弹出密码框) + */ + async execAsAdmin( + command: string, + options: PrivilegeOptions + ): Promise { + // 先检查是否真的需要提权 + if (await this.hasAdminPrivilege()) { + // 已经是 admin,直接执行 + return this.admin.exec(command); + } + + // 显示确认对话框 + const confirmed = await this.showConfirmDialog(options.reason); + if (!confirmed) { + return { success: false, error: 'User cancelled' }; + } + + // 通过平台特定方式获取权限并执行 + return this.admin.execWithPrivilege(command, options); + } + + /** + * 显示权限请求确认对话框 + */ + private async showConfirmDialog(reason: string): Promise { + const result = await dialog.showMessageBox({ + type: 'warning', + title: '需要管理员权限', + message: 'ClawX 需要管理员权限来完成此操作', + detail: `${reason}\n\n点击"继续"后将弹出系统密码输入框。`, + buttons: ['继续', '取消'], + defaultId: 0, + cancelId: 1, + }); + + return result.response === 0; + } +} + +export const privilegeManager = new PrivilegeManager(); +``` + +#### 2.7.4 macOS 实现 (osascript) + +```typescript +// electron/privilege/darwin-admin.ts + +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +export class DarwinAdmin { + /** + * 检查是否以 root 运行 + */ + async hasAdminPrivilege(): Promise { + try { + await execAsync('test -w /usr/local/bin'); + return true; + } catch { + return false; + } + } + + /** + * 直接执行命令 + */ + async exec(command: string): Promise { + try { + const { stdout, stderr } = await execAsync(command); + return { success: true, stdout, stderr }; + } catch (error: any) { + return { success: false, error: error.message }; + } + } + + /** + * 通过 osascript 弹出系统密码框执行命令 + * 这会显示 macOS 原生的授权对话框 + */ + async execWithPrivilege( + command: string, + options: PrivilegeOptions + ): Promise { + // 使用 osascript 调用 Authorization Services + // 这会弹出 macOS 原生密码输入框 + const escapedCommand = command.replace(/"/g, '\\"'); + const escapedReason = options.reason.replace(/"/g, '\\"'); + + const appleScript = ` + do shell script "${escapedCommand}" \\ + with administrator privileges \\ + with prompt "${escapedReason}" + `; + + try { + const { stdout, stderr } = await execAsync( + `osascript -e '${appleScript.replace(/'/g, "'\"'\"'")}'`, + { timeout: options.timeout || 60000 } + ); + return { success: true, stdout, stderr }; + } catch (error: any) { + // 用户取消会抛出错误 + if (error.message.includes('User canceled')) { + return { success: false, error: 'User cancelled' }; + } + return { success: false, error: error.message }; + } + } +} +``` + +#### 2.7.5 Windows 实现 (UAC) + +```typescript +// electron/privilege/win32-admin.ts + +import { exec, spawn } from 'child_process'; +import { promisify } from 'util'; +import { writeFileSync, unlinkSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +const execAsync = promisify(exec); + +export class Win32Admin { + /** + * 检查是否以管理员运行 + */ + async hasAdminPrivilege(): Promise { + try { + // 尝试写入 System32,如果成功说明有管理员权限 + await execAsync('fsutil dirty query %systemdrive%'); + return true; + } catch { + return false; + } + } + + async exec(command: string): Promise { + try { + const { stdout, stderr } = await execAsync(command, { shell: 'powershell.exe' }); + return { success: true, stdout, stderr }; + } catch (error: any) { + return { success: false, error: error.message }; + } + } + + /** + * 通过 UAC 提升权限执行命令 + * 这会弹出 Windows UAC 确认对话框 + */ + async execWithPrivilege( + command: string, + options: PrivilegeOptions + ): Promise { + return new Promise((resolve) => { + // 创建临时 PowerShell 脚本 + const scriptPath = join(tmpdir(), `clawx-admin-${Date.now()}.ps1`); + const outputPath = join(tmpdir(), `clawx-admin-${Date.now()}.txt`); + + // PowerShell 脚本内容 + const script = ` + try { + ${command} + "SUCCESS" | Out-File -FilePath "${outputPath}" + } catch { + $_.Exception.Message | Out-File -FilePath "${outputPath}" + exit 1 + } + `; + + writeFileSync(scriptPath, script, 'utf-8'); + + // 使用 PowerShell 的 Start-Process 触发 UAC + const elevateCommand = ` + Start-Process powershell.exe \ + -ArgumentList '-ExecutionPolicy Bypass -File "${scriptPath}"' \ + -Verb RunAs \ + -Wait + `; + + const child = spawn('powershell.exe', ['-Command', elevateCommand], { + stdio: 'pipe', + }); + + child.on('close', (code) => { + try { + const output = require('fs').readFileSync(outputPath, 'utf-8').trim(); + unlinkSync(scriptPath); + unlinkSync(outputPath); + + if (output === 'SUCCESS') { + resolve({ success: true }); + } else { + resolve({ success: false, error: output }); + } + } catch (error: any) { + resolve({ success: false, error: 'UAC cancelled or failed' }); + } + }); + }); + } +} +``` + +#### 2.7.6 Linux 实现 (pkexec/polkit) + +```typescript +// electron/privilege/linux-admin.ts + +import { exec, spawn } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +export class LinuxAdmin { + async hasAdminPrivilege(): Promise { + return process.getuid?.() === 0; + } + + async exec(command: string): Promise { + try { + const { stdout, stderr } = await execAsync(command); + return { success: true, stdout, stderr }; + } catch (error: any) { + return { success: false, error: error.message }; + } + } + + /** + * 通过 pkexec 弹出图形化密码框 + * pkexec 是 polkit 的一部分,大多数 Linux 桌面都支持 + */ + async execWithPrivilege( + command: string, + options: PrivilegeOptions + ): Promise { + return new Promise((resolve) => { + // 检查 pkexec 是否可用 + exec('which pkexec', (error) => { + if (error) { + // 回退到 gksudo 或 kdesudo + this.execWithFallback(command, options).then(resolve); + return; + } + + // 使用 pkexec (会弹出 polkit 密码框) + const child = spawn('pkexec', ['bash', '-c', command], { + stdio: ['ignore', 'pipe', 'pipe'], + env: { + ...process.env, + // 设置 polkit 显示的描述 + POLKIT_AGENT_HELPER_NAME: 'ClawX', + }, + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { stdout += data; }); + child.stderr.on('data', (data) => { stderr += data; }); + + child.on('close', (code) => { + if (code === 0) { + resolve({ success: true, stdout, stderr }); + } else if (code === 126) { + // 用户取消 + resolve({ success: false, error: 'User cancelled' }); + } else { + resolve({ success: false, error: stderr || `Exit code: ${code}` }); + } + }); + }); + }); + } + + /** + * pkexec 不可用时的回退方案 + */ + private async execWithFallback( + command: string, + options: PrivilegeOptions + ): Promise { + // 尝试 gksudo (GNOME) 或 kdesudo (KDE) + const fallbacks = ['gksudo', 'kdesudo', 'sudo -A']; + + for (const fallback of fallbacks) { + try { + const { stdout, stderr } = await execAsync(`${fallback} ${command}`); + return { success: true, stdout, stderr }; + } catch { + continue; + } + } + + return { + success: false, + error: 'No GUI sudo helper available. Please run ClawX as root.' + }; + } +} +``` + +#### 2.7.7 使用示例 + +```typescript +// electron/installer/node-installer.ts + +import { privilegeManager } from '../privilege'; + +export async function installNodeGlobally(): Promise { + const isNodeInstalled = await checkNodeInstalled(); + + if (!isNodeInstalled) { + // 通过 GUI 弹窗获取管理员权限安装 Node.js + const result = await privilegeManager.execAsAdmin( + // macOS: 使用 Homebrew + process.platform === 'darwin' + ? '/opt/homebrew/bin/brew install node@22' + // Windows: 使用 winget + : process.platform === 'win32' + ? 'winget install OpenJS.NodeJS.LTS' + // Linux: 使用包管理器 + : 'apt-get install -y nodejs', + { + reason: '安装 Node.js 运行时环境,这是 ClawX 运行的必要组件。', + } + ); + + if (!result.success) { + throw new Error(`Node.js 安装失败: ${result.error}`); + } + } +} +``` + +### 2.8 GUI 环境变量配置 + +ClawX 需要管理多种环境变量(API Keys、PATH、代理设置等),必须通过**图形界面**配置,而非要求用户编辑 `.env` 文件或终端。 + +#### 2.8.1 环境变量分类 + +| 类型 | 示例 | 存储位置 | 安全级别 | +|------|------|----------|----------| +| **API Keys** | `ANTHROPIC_API_KEY` | 系统密钥链 | 🔐 加密存储 | +| **PATH** | Node.js/npm 路径 | Shell Profile | 普通 | +| **代理** | `HTTP_PROXY` | 应用配置 | 普通 | +| **应用配置** | `CLAWX_PORT` | 应用配置 | 普通 | + +#### 2.8.2 环境配置管理器 + +```typescript +// electron/env-config/index.ts + +import { safeStorage, app } from 'electron'; +import Store from 'electron-store'; +import { PathManager } from './path-manager'; +import { ApiKeyManager } from './api-keys'; +import { ShellProfileManager } from './shell-profile'; + +export interface EnvConfig { + // API Keys (加密存储) + apiKeys: { + anthropic?: string; + openai?: string; + google?: string; + [key: string]: string | undefined; + }; + + // 代理设置 + proxy: { + http?: string; + https?: string; + noProxy?: string[]; + }; + + // 应用配置 + app: { + port?: number; + logLevel?: string; + dataDir?: string; + }; +} + +export class EnvConfigManager { + private store: Store; + private pathManager: PathManager; + private apiKeyManager: ApiKeyManager; + private shellProfileManager: ShellProfileManager; + + constructor() { + this.store = new Store({ name: 'env-config' }); + this.pathManager = new PathManager(); + this.apiKeyManager = new ApiKeyManager(); + this.shellProfileManager = new ShellProfileManager(); + } + + /** + * 获取所有环境配置 (API Keys 脱敏) + */ + getConfig(): EnvConfig { + return { + apiKeys: this.apiKeyManager.getMaskedKeys(), + proxy: this.store.get('proxy', {}) as EnvConfig['proxy'], + app: this.store.get('app', {}) as EnvConfig['app'], + }; + } + + /** + * 设置 API Key (自动加密存储) + */ + async setApiKey(provider: string, key: string): Promise { + await this.apiKeyManager.setKey(provider, key); + + // 同步到 OpenClaw 配置 + await this.syncToOpenClaw(); + } + + /** + * 设置代理 + */ + setProxy(proxy: EnvConfig['proxy']): void { + this.store.set('proxy', proxy); + this.applyProxy(proxy); + } + + /** + * 确保 PATH 包含必要的路径 + */ + async ensurePath(): Promise { + const requiredPaths = [ + '/usr/local/bin', + '/opt/homebrew/bin', // macOS ARM + `${app.getPath('home')}/.nvm/versions/node/*/bin`, // nvm + `${app.getPath('home')}/.local/bin`, // pip install --user + ]; + + for (const p of requiredPaths) { + await this.pathManager.addToPath(p); + } + } + + /** + * 同步配置到 OpenClaw + */ + private async syncToOpenClaw(): Promise { + const openclawConfigPath = `${app.getPath('home')}/.openclaw/config.json`; + // 读取现有配置,合并 API keys,写回 + // ... + } + + private applyProxy(proxy: EnvConfig['proxy']): void { + if (proxy.http) process.env.HTTP_PROXY = proxy.http; + if (proxy.https) process.env.HTTPS_PROXY = proxy.https; + if (proxy.noProxy) process.env.NO_PROXY = proxy.noProxy.join(','); + } +} + +export const envConfigManager = new EnvConfigManager(); +``` + +#### 2.8.3 API Key 安全存储 + +```typescript +// electron/env-config/api-keys.ts + +import { safeStorage } from 'electron'; +import Store from 'electron-store'; +import keytar from 'keytar'; + +const SERVICE_NAME = 'ClawX'; + +/** + * API Key 管理器 + * 使用系统密钥链安全存储敏感信息 + */ +export class ApiKeyManager { + private store: Store; + + constructor() { + this.store = new Store({ name: 'api-keys-meta' }); + } + + /** + * 安全存储 API Key + * - macOS: 使用 Keychain + * - Windows: 使用 Credential Manager + * - Linux: 使用 libsecret (GNOME Keyring / KWallet) + */ + async setKey(provider: string, key: string): Promise { + try { + // 首选: 系统密钥链 (通过 keytar) + await keytar.setPassword(SERVICE_NAME, provider, key); + this.store.set(`providers.${provider}`, { stored: true, method: 'keychain' }); + } catch { + // 回退: Electron safeStorage (本地加密) + if (safeStorage.isEncryptionAvailable()) { + const encrypted = safeStorage.encryptString(key); + this.store.set(`providers.${provider}`, { + stored: true, + method: 'safeStorage', + encrypted: encrypted.toString('base64'), + }); + } else { + throw new Error('No secure storage available'); + } + } + } + + /** + * 获取 API Key + */ + async getKey(provider: string): Promise { + const meta = this.store.get(`providers.${provider}`) as any; + if (!meta?.stored) return null; + + if (meta.method === 'keychain') { + return keytar.getPassword(SERVICE_NAME, provider); + } else if (meta.method === 'safeStorage' && meta.encrypted) { + const buffer = Buffer.from(meta.encrypted, 'base64'); + return safeStorage.decryptString(buffer); + } + + return null; + } + + /** + * 删除 API Key + */ + async deleteKey(provider: string): Promise { + const meta = this.store.get(`providers.${provider}`) as any; + if (!meta?.stored) return; + + if (meta.method === 'keychain') { + await keytar.deletePassword(SERVICE_NAME, provider); + } + + this.store.delete(`providers.${provider}`); + } + + /** + * 获取所有已配置的 Key (脱敏显示) + */ + getMaskedKeys(): Record { + const result: Record = {}; + const providers = this.store.get('providers', {}) as Record; + + for (const [provider, meta] of Object.entries(providers)) { + if (meta?.stored) { + result[provider] = '••••••••••••'; // 脱敏显示 + } + } + + return result; + } + + /** + * 检查 Key 是否已配置 + */ + hasKey(provider: string): boolean { + const meta = this.store.get(`providers.${provider}`) as any; + return meta?.stored === true; + } +} +``` + +#### 2.8.4 PATH 环境变量管理 + +```typescript +// electron/env-config/path-manager.ts + +import { exec } from 'child_process'; +import { promisify } from 'util'; +import { existsSync, writeFileSync, readFileSync } from 'fs'; +import { join } from 'path'; +import { homedir, platform } from 'os'; +import { privilegeManager } from '../privilege'; + +const execAsync = promisify(exec); + +export class PathManager { + /** + * 添加路径到 PATH 环境变量 + * 通过 GUI 完成,用户无需手动编辑 shell 配置文件 + */ + async addToPath(newPath: string): Promise { + // 检查路径是否已存在 + const currentPath = process.env.PATH || ''; + if (currentPath.includes(newPath)) return; + + // 检查路径是否有效 + if (!existsSync(newPath) && !newPath.includes('*')) return; + + switch (platform()) { + case 'darwin': + await this.addToPathMacOS(newPath); + break; + case 'win32': + await this.addToPathWindows(newPath); + break; + case 'linux': + await this.addToPathLinux(newPath); + break; + } + } + + /** + * macOS: 添加到 /etc/paths.d/ (系统级) 或 shell profile (用户级) + */ + private async addToPathMacOS(newPath: string): Promise { + // 优先使用用户级配置 (~/.zshrc 或 ~/.bash_profile) + const shellProfile = this.getShellProfile(); + + if (shellProfile) { + const content = existsSync(shellProfile) + ? readFileSync(shellProfile, 'utf-8') + : ''; + + if (!content.includes(newPath)) { + const exportLine = `\nexport PATH="${newPath}:$PATH"\n`; + writeFileSync(shellProfile, content + exportLine); + } + } else { + // 回退到系统级 (需要管理员权限) + const pathFile = `/etc/paths.d/clawx`; + await privilegeManager.execAsAdmin( + `echo "${newPath}" >> ${pathFile}`, + { reason: '配置系统 PATH 环境变量' } + ); + } + } + + /** + * Windows: 修改用户环境变量 (通过注册表) + */ + private async addToPathWindows(newPath: string): Promise { + // 使用 PowerShell 修改用户级 PATH (无需管理员权限) + const command = ` + $currentPath = [Environment]::GetEnvironmentVariable("Path", "User") + if ($currentPath -notlike "*${newPath}*") { + [Environment]::SetEnvironmentVariable("Path", "${newPath};$currentPath", "User") + } + `; + + await execAsync(`powershell -Command "${command}"`); + + // 通知系统环境变量已更改 + await execAsync('setx CLAWX_PATH_UPDATED 1'); + } + + /** + * Linux: 添加到 shell profile + */ + private async addToPathLinux(newPath: string): Promise { + const shellProfile = this.getShellProfile(); + + if (shellProfile) { + const content = existsSync(shellProfile) + ? readFileSync(shellProfile, 'utf-8') + : ''; + + if (!content.includes(newPath)) { + const exportLine = `\nexport PATH="${newPath}:$PATH"\n`; + writeFileSync(shellProfile, content + exportLine); + } + } + } + + /** + * 获取当前用户的 shell profile 文件 + */ + private getShellProfile(): string | null { + const home = homedir(); + const shell = process.env.SHELL || ''; + + if (shell.includes('zsh')) { + return join(home, '.zshrc'); + } else if (shell.includes('bash')) { + const bashProfile = join(home, '.bash_profile'); + const bashrc = join(home, '.bashrc'); + return existsSync(bashProfile) ? bashProfile : bashrc; + } else if (shell.includes('fish')) { + return join(home, '.config/fish/config.fish'); + } + + // 默认 + return join(home, '.profile'); + } +} +``` + +#### 2.8.5 设置页面 UI + +```typescript +// src/pages/Settings/ProviderSettings.tsx + +import { useState, useEffect } from 'react'; +import { Eye, EyeOff, Check, AlertCircle } from 'lucide-react'; + +interface ApiKeyInputProps { + provider: string; + label: string; + placeholder: string; + hasKey: boolean; + onSave: (key: string) => Promise; + onDelete: () => Promise; +} + +function ApiKeyInput({ provider, label, placeholder, hasKey, onSave, onDelete }: ApiKeyInputProps) { + const [value, setValue] = useState(''); + const [showKey, setShowKey] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + const handleSave = async () => { + if (!value.trim()) return; + + setSaving(true); + setError(null); + + try { + await onSave(value); + setValue(''); // 清空输入,显示"已配置"状态 + } catch (e: any) { + setError(e.message); + } finally { + setSaving(false); + } + }; + + return ( +
+ + + {hasKey ? ( + // 已配置状态 +
+ + 已配置 + •••••••••••• +
+ + +
+ ) : ( + // 未配置状态 +
+
+ setValue(e.target.value)} + placeholder={placeholder} + className="pr-10" + /> + +
+ +
+ )} + + {error && ( +
+ + {error} +
+ )} + +

+ API Key 将安全存储在系统密钥链中,不会以明文保存。 +

+
+ ); +} + +export function ProviderSettings() { + const [config, setConfig] = useState(null); + + useEffect(() => { + // 从主进程获取配置 + window.electron.ipcRenderer.invoke('env:getConfig').then(setConfig); + }, []); + + const handleSaveApiKey = async (provider: string, key: string) => { + await window.electron.ipcRenderer.invoke('env:setApiKey', provider, key); + // 刷新配置 + const newConfig = await window.electron.ipcRenderer.invoke('env:getConfig'); + setConfig(newConfig); + }; + + const handleDeleteApiKey = async (provider: string) => { + await window.electron.ipcRenderer.invoke('env:deleteApiKey', provider); + const newConfig = await window.electron.ipcRenderer.invoke('env:getConfig'); + setConfig(newConfig); + }; + + if (!config) return ; + + return ( + + + AI 模型配置 + + 配置您的 AI 服务提供商 API Key + + + + handleSaveApiKey('anthropic', key)} + onDelete={() => handleDeleteApiKey('anthropic')} + /> + + + + handleSaveApiKey('openai', key)} + onDelete={() => handleDeleteApiKey('openai')} + /> + + + + handleSaveApiKey('google', key)} + onDelete={() => handleDeleteApiKey('google')} + /> + + + ); +} +``` + +#### 2.8.6 安装向导集成 + +```typescript +// src/pages/Setup/ProviderStep.tsx + +export function ProviderStep({ onNext, onBack }: StepProps) { + const [selectedProvider, setSelectedProvider] = useState('anthropic'); + const [apiKey, setApiKey] = useState(''); + const [validating, setValidating] = useState(false); + const [error, setError] = useState(null); + + const providers = [ + { id: 'anthropic', name: 'Anthropic', model: 'Claude', icon: '🤖' }, + { id: 'openai', name: 'OpenAI', model: 'GPT-4', icon: '💚' }, + { id: 'google', name: 'Google', model: 'Gemini', icon: '🔷' }, + ]; + + const handleNext = async () => { + if (!apiKey.trim()) { + setError('请输入 API Key'); + return; + } + + setValidating(true); + setError(null); + + try { + // 验证 API Key 是否有效 + const valid = await window.electron.ipcRenderer.invoke( + 'provider:validateKey', + selectedProvider, + apiKey + ); + + if (!valid) { + setError('API Key 无效,请检查后重试'); + return; + } + + // 安全存储 API Key + await window.electron.ipcRenderer.invoke( + 'env:setApiKey', + selectedProvider, + apiKey + ); + + onNext({ provider: selectedProvider }); + } catch (e: any) { + setError(e.message); + } finally { + setValidating(false); + } + }; + + return ( +
+
+ +
+ {providers.map((p) => ( + setSelectedProvider(p.id)} + > +
+ {p.icon} +

{p.name}

+

{p.model}

+
+
+ ))} +
+
+ +
+ + setApiKey(e.target.value)} + placeholder={`输入您的 ${providers.find(p => p.id === selectedProvider)?.name} API Key`} + /> + {error && ( +

{error}

+ )} +

+ 您的 API Key 将安全存储在系统密钥链中,不会上传到任何服务器。 +

+
+ +
+ + +
+
+ ); +} +``` + +--- + +## 三、UI/UX 设计规范 + +### 3.1 设计原则 + +| 原则 | 描述 | +|------|------| +| **简洁优先** | 隐藏复杂性,暴露必要功能 | +| **渐进式披露** | 基础功能易达,高级功能可探索 | +| **一致性** | 跨页面/组件保持视觉和交互一致 | +| **响应式反馈** | 所有操作有即时视觉反馈 | +| **原生感** | 遵循各平台设计语言 | + +### 3.2 色彩系统 + +```css +/* src/styles/themes/tokens.css */ +:root { + /* Primary */ + --color-primary-50: #eff6ff; + --color-primary-500: #3b82f6; + --color-primary-600: #2563eb; + + /* Semantic */ + --color-success: #22c55e; + --color-warning: #f59e0b; + --color-error: #ef4444; + --color-info: #3b82f6; + + /* Neutral (Light) */ + --color-bg-primary: #ffffff; + --color-bg-secondary: #f8fafc; + --color-text-primary: #0f172a; + --color-text-secondary: #64748b; + --color-border: #e2e8f0; +} + +[data-theme="dark"] { + --color-bg-primary: #0f172a; + --color-bg-secondary: #1e293b; + --color-text-primary: #f8fafc; + --color-text-secondary: #94a3b8; + --color-border: #334155; +} +``` + +### 3.3 核心页面线框图 + +``` +┌─────────────────────────────────────────────────────────┐ +│ ClawX ─ □ × │ +├──────────┬──────────────────────────────────────────────┤ +│ │ │ +│ ● 概览 │ ┌─────────────┐ ┌─────────────┐ │ +│ ○ 对话 │ │ Gateway │ │ Channels │ │ +│ ○ 通道 │ │ ● 运行中 │ │ 3 已连接 │ │ +│ ○ 技能 │ └─────────────┘ └─────────────┘ │ +│ ○ 设置 │ │ +│ ○ 定时任务│ ┌───────────────────────────────────────┐ │ +│ │ │ 最近对话 │ │ +│ │ ├───────────────────────────────────────┤ │ +│ │ │ 📱 WhatsApp · 用户A · 2分钟前 │ │ +│ │ │ 💬 Telegram · 用户B · 15分钟前 │ │ +│ │ │ 💬 Discord · 用户C · 1小时前 │ │ +│ │ └───────────────────────────────────────┘ │ +│ │ │ +│ │ ┌───────────────────────────────────────┐ │ +│ │ │ 快捷操作 │ │ +│ │ │ [添加通道] [浏览技能] [查看日志] │ │ +│ │ └───────────────────────────────────────┘ │ +│ │ │ +├──────────┴──────────────────────────────────────────────┤ +│ Gateway: 运行中 · 3 通道 · 12 技能 · v2026.2.3 │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3.4 定时任务页面设计 + +``` +┌─────────────────────────────────────────────────────────┐ +│ ClawX ─ □ × │ +├──────────┬──────────────────────────────────────────────┤ +│ │ │ +│ ○ 概览 │ 定时任务 [+ 新建任务] │ +│ ○ 对话 │ │ +│ ○ 通道 │ ┌───────────────────────────────────────┐ │ +│ ○ 技能 │ │ 📋 每日天气播报 ● 已启用 │ │ +│ ● 定时 │ │ ⏰ 每天 08:00 │ │ +│ ○ 设置 │ │ 📍 发送到: WhatsApp - 家庭群 │ │ +│ │ │ 上次执行: 今天 08:00 ✓ │ │ +│ │ │ [编辑] [暂停] [删除] │ │ +│ │ └───────────────────────────────────────┘ │ +│ │ │ +│ │ ┌───────────────────────────────────────┐ │ +│ │ │ 📊 周报汇总 ○ 已暂停 │ │ +│ │ │ ⏰ 每周五 18:00 │ │ +│ │ │ 📍 发送到: Telegram - 工作频道 │ │ +│ │ │ 上次执行: 上周五 18:00 ✓ │ │ +│ │ │ [编辑] [启用] [删除] │ │ +│ │ └───────────────────────────────────────┘ │ +│ │ │ +│ │ ┌───────────────────────────────────────┐ │ +│ │ │ 🔔 服务器健康检查 ● 已启用 │ │ +│ │ │ ⏰ 每 30 分钟 │ │ +│ │ │ 📍 发送到: Discord - 运维通知 │ │ +│ │ │ 上次执行: 10分钟前 ✓ │ │ +│ │ │ [编辑] [暂停] [删除] │ │ +│ │ └───────────────────────────────────────┘ │ +│ │ │ +├──────────┴──────────────────────────────────────────────┤ +│ 3 个任务 · 2 个运行中 · 1 个暂停 │ +└─────────────────────────────────────────────────────────┘ +``` + +#### 定时任务编辑器弹窗 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 新建定时任务 [×] │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 任务名称 │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 每日天气播报 │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ 执行内容 (发送给 AI 的消息) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ 请查询北京今天的天气,并生成一条适合发送到群里 │ │ +│ │ 的天气播报消息,包含穿衣建议。 │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ 执行时间 │ +│ ┌──────────────┐ ┌────────────────────────────┐ │ +│ │ ○ 每天 │ │ 08 : 00 │ │ +│ │ ○ 每周 │ └────────────────────────────┘ │ +│ │ ○ 每月 │ │ +│ │ ○ 自定义Cron │ Cron: 0 8 * * * │ +│ └──────────────┘ │ +│ │ +│ 发送到 │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ WhatsApp - 家庭群 ▼ │ │ +│ └─────────────────────────────────────────────────┘ │ +│ │ +│ ☑ 启用任务 │ +│ │ +│ [取消] [保存] │ +└─────────────────────────────────────────────────────────┘ +``` + +#### 定时任务类型定义 + +```typescript +// src/types/cron.ts + +export interface CronJob { + /** 任务 ID */ + id: string; + + /** 任务名称 */ + name: string; + + /** 发送给 AI 的消息内容 */ + message: string; + + /** Cron 表达式 */ + schedule: string; + + /** 目标通道 */ + target: { + channelType: 'whatsapp' | 'telegram' | 'discord' | 'slack'; + channelId: string; + channelName: string; + }; + + /** 是否启用 */ + enabled: boolean; + + /** 创建时间 */ + createdAt: string; + + /** 更新时间 */ + updatedAt: string; + + /** 上次执行信息 */ + lastRun?: { + time: string; + success: boolean; + error?: string; + }; + + /** 下次执行时间 */ + nextRun?: string; +} + +export interface CronJobCreateInput { + name: string; + message: string; + schedule: string; + target: CronJob['target']; + enabled?: boolean; +} + +export interface CronJobUpdateInput { + name?: string; + message?: string; + schedule?: string; + target?: CronJob['target']; + enabled?: boolean; +} +``` + +#### 定时任务 Hook + +```typescript +// src/hooks/useCron.ts + +import { useState, useEffect, useCallback } from 'react'; +import type { CronJob, CronJobCreateInput, CronJobUpdateInput } from '@/types/cron'; + +export function useCron() { + const [jobs, setJobs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 加载任务列表 + const fetchJobs = useCallback(async () => { + try { + setLoading(true); + const result = await window.electron.ipcRenderer.invoke('cron:list'); + setJobs(result); + setError(null); + } catch (e: any) { + setError(e.message); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchJobs(); + + // 监听任务更新事件 + const handleUpdate = (_: any, updatedJobs: CronJob[]) => { + setJobs(updatedJobs); + }; + + window.electron.ipcRenderer.on('cron:updated', handleUpdate); + + return () => { + window.electron.ipcRenderer.off('cron:updated', handleUpdate); + }; + }, [fetchJobs]); + + // 创建任务 + const createJob = async (input: CronJobCreateInput): Promise => { + const job = await window.electron.ipcRenderer.invoke('cron:create', input); + await fetchJobs(); + return job; + }; + + // 更新任务 + const updateJob = async (id: string, input: CronJobUpdateInput): Promise => { + await window.electron.ipcRenderer.invoke('cron:update', id, input); + await fetchJobs(); + }; + + // 删除任务 + const deleteJob = async (id: string): Promise => { + await window.electron.ipcRenderer.invoke('cron:delete', id); + await fetchJobs(); + }; + + // 启用/禁用任务 + const toggleJob = async (id: string, enabled: boolean): Promise => { + await window.electron.ipcRenderer.invoke('cron:toggle', id, enabled); + await fetchJobs(); + }; + + // 手动触发任务 + const triggerJob = async (id: string): Promise => { + await window.electron.ipcRenderer.invoke('cron:trigger', id); + }; + + return { + jobs, + loading, + error, + createJob, + updateJob, + deleteJob, + toggleJob, + triggerJob, + refresh: fetchJobs, + }; +} +``` + +#### 定时任务页面组件 + +```typescript +// src/pages/Cron/index.tsx + +import { useState } from 'react'; +import { useCron } from '@/hooks/useCron'; +import { CronJobCard } from './CronJobCard'; +import { CronEditor } from './CronEditor'; +import { Plus, Clock } from 'lucide-react'; + +export function CronPage() { + const { jobs, loading, createJob, updateJob, deleteJob, toggleJob } = useCron(); + const [editorOpen, setEditorOpen] = useState(false); + const [editingJob, setEditingJob] = useState(null); + + const activeJobs = jobs.filter(j => j.enabled); + const pausedJobs = jobs.filter(j => !j.enabled); + + const handleCreate = () => { + setEditingJob(null); + setEditorOpen(true); + }; + + const handleEdit = (job: CronJob) => { + setEditingJob(job); + setEditorOpen(true); + }; + + const handleSave = async (input: CronJobCreateInput) => { + if (editingJob) { + await updateJob(editingJob.id, input); + } else { + await createJob(input); + } + setEditorOpen(false); + }; + + if (loading) return ; + + return ( +
+ {/* 页头 */} +
+
+

定时任务

+

+ 设置自动执行的 AI 任务,按计划发送消息 +

+
+ +
+ + {/* 统计卡片 */} +
+ + +
+
+ +
+
+

{jobs.length}

+

总任务数

+
+
+
+
+ + +
+
+ +
+
+

{activeJobs.length}

+

运行中

+
+
+
+
+ + +
+
+ +
+
+

{pausedJobs.length}

+

已暂停

+
+
+
+
+
+ + {/* 任务列表 */} + {jobs.length === 0 ? ( + + +

暂无定时任务

+

+ 创建您的第一个定时任务,让 AI 自动为您工作 +

+ +
+ ) : ( +
+ {jobs.map(job => ( + handleEdit(job)} + onDelete={() => deleteJob(job.id)} + onToggle={(enabled) => toggleJob(job.id, enabled)} + /> + ))} +
+ )} + + {/* 编辑器弹窗 */} + setEditorOpen(false)} + onSave={handleSave} + initialData={editingJob} + /> +
+ ); +} +``` + +#### Cron 表达式选择器 + +```typescript +// src/pages/Cron/CronSchedulePicker.tsx + +import { useState, useEffect } from 'react'; + +interface CronSchedulePickerProps { + value: string; + onChange: (cron: string) => void; +} + +type ScheduleType = 'daily' | 'weekly' | 'monthly' | 'interval' | 'custom'; + +export function CronSchedulePicker({ value, onChange }: CronSchedulePickerProps) { + const [type, setType] = useState('daily'); + const [time, setTime] = useState('08:00'); + const [weekday, setWeekday] = useState(1); // 周一 + const [monthday, setMonthday] = useState(1); + const [interval, setInterval] = useState(30); // 分钟 + const [customCron, setCustomCron] = useState(value); + + // 解析现有 cron 表达式 + useEffect(() => { + // 简单解析,实际可用 cron-parser 库 + if (value.match(/^\d+ \d+ \* \* \*$/)) { + setType('daily'); + const [min, hour] = value.split(' '); + setTime(`${hour.padStart(2, '0')}:${min.padStart(2, '0')}`); + } + // ... 其他模式解析 + }, []); + + // 生成 cron 表达式 + useEffect(() => { + let cron = ''; + const [hour, min] = time.split(':'); + + switch (type) { + case 'daily': + cron = `${parseInt(min)} ${parseInt(hour)} * * *`; + break; + case 'weekly': + cron = `${parseInt(min)} ${parseInt(hour)} * * ${weekday}`; + break; + case 'monthly': + cron = `${parseInt(min)} ${parseInt(hour)} ${monthday} * *`; + break; + case 'interval': + cron = `*/${interval} * * * *`; + break; + case 'custom': + cron = customCron; + break; + } + + if (cron !== value) { + onChange(cron); + } + }, [type, time, weekday, monthday, interval, customCron]); + + const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + + return ( +
+ {/* 类型选择 */} + setType(v as ScheduleType)}> +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* 时间选择 */} + {(type === 'daily' || type === 'weekly' || type === 'monthly') && ( +
+ + setTime(e.target.value)} + className="w-32" + /> +
+ )} + + {/* 周几选择 */} + {type === 'weekly' && ( +
+ + +
+ )} + + {/* 日期选择 */} + {type === 'monthly' && ( +
+ + +
+ )} + + {/* 间隔选择 */} + {type === 'interval' && ( +
+ + setInterval(parseInt(e.target.value))} + className="w-20" + /> + 分钟 +
+ )} + + {/* 自定义 Cron */} + {type === 'custom' && ( +
+ setCustomCron(e.target.value)} + placeholder="分 时 日 月 周 (如: 0 8 * * 1-5)" + /> +

+ Cron 表达式格式: 分钟(0-59) 小时(0-23) 日(1-31) 月(1-12) 周(0-6) +

+
+ )} + + {/* 预览 */} +
+

+ Cron 表达式: + {value} +

+

+ 下次执行: + {getNextRunTime(value)} +

+
+
+ ); +} + +function getNextRunTime(cron: string): string { + // 使用 croner 或类似库计算下次执行时间 + // 这里简化处理 + return '今天 08:00'; +} +``` + +--- + +## 四、版本规划 + +### 4.1 版本号策略 + +``` +ClawX 版本: X.Y.Z[-prerelease] + │ │ │ + │ │ └── Patch: Bug 修复、性能优化 + │ └──── Minor: 新功能、新技能包、UI 改进 + └────── Major: 重大变更、不兼容更新 + +示例: +- 1.0.0 首个稳定版 +- 1.1.0 新增技能市场功能 +- 1.1.1 修复 Windows 安装问题 +- 2.0.0 UI 重构、新架构 +- 1.2.0-beta.1 下一版本测试版 +``` + +### 4.2 OpenClaw 兼容性矩阵 + +| ClawX 版本 | OpenClaw 版本 | Node.js 版本 | 备注 | +|------------|---------------|--------------|------| +| 1.0.x | 2026.2.x | 22.x | 首发版本 | +| 1.1.x | 2026.3.x | 22.x | 功能增强 | +| 2.0.x | 2026.6.x | 24.x | 大版本升级 | + +### 4.3 里程碑规划 + +#### 🚀 v0.1.0 - Alpha (内部测试) + +**目标**: 核心架构验证 + +| 任务 | 优先级 | 状态 | +|------|--------|------| +| Electron + React 项目骨架 | P0 | ⬜ | +| Gateway 进程管理 | P0 | ⬜ | +| 基础 UI 框架 (侧边栏/布局) | P0 | ⬜ | +| WebSocket 通信层 | P0 | ⬜ | +| 状态管理 (Zustand) | P1 | ⬜ | + +**交付物**: +- 可运行的桌面应用 +- 能启动/停止 Gateway +- 基础 Dashboard 页面 + +--- + +#### 🎯 v0.5.0 - Beta (公开测试) + +**目标**: 安装向导 MVP + +| 任务 | 优先级 | 状态 | +|------|--------|------| +| 安装向导 UI | P0 | ⬜ | +| Node.js 自动检测/安装 | P0 | ⬜ | +| openclaw npm 安装 | P0 | ⬜ | +| Provider 配置 (API Key) | P0 | ⬜ | +| 首个通道连接 (WhatsApp QR) | P1 | ⬜ | +| 错误处理与提示 | P1 | ⬜ | + +**交付物**: +- 完整安装向导流程 +- 支持 macOS (Apple Silicon + Intel) +- 可配置 Anthropic/OpenAI + +--- + +#### 📦 v1.0.0 - Stable (首个正式版) + +**目标**: 生产可用 + +| 任务 | 优先级 | 状态 | +|------|--------|------| +| 完整 Dashboard | P0 | ⬜ | +| 通道管理页面 | P0 | ⬜ | +| 对话界面 | P0 | ⬜ | +| 技能浏览/启用 | P0 | ⬜ | +| 设置页面 | P0 | ⬜ | +| 预装技能包选择 | P1 | ⬜ | +| 自动更新 (Sparkle/electron-updater) | P1 | ⬜ | +| Windows 支持 | P1 | ⬜ | +| 深色模式 | P2 | ⬜ | +| 崩溃报告 | P2 | ⬜ | + +**交付物**: +- macOS + Windows 安装包 +- 自动更新能力 +- 用户文档 + +--- + +#### 🌟 v1.1.0 - 功能增强 + +**目标**: 技能生态 + +| 任务 | 优先级 | 状态 | +|------|--------|------| +| 技能市场 UI | P0 | ⬜ | +| 在线技能安装 | P0 | ⬜ | +| 技能配置界面 | P1 | ⬜ | +| 技能使用统计 | P2 | ⬜ | +| Linux 支持 | P2 | ⬜ | + +--- + +#### 🚀 v2.0.0 - 重大升级 + +**目标**: 多 Agent / 高级功能 + +| 任务 | 优先级 | 状态 | +|------|--------|------| +| 多 Agent 支持 | P0 | ⬜ | +| 工作流编排 | P1 | ⬜ | +| 插件 SDK | P1 | ⬜ | +| 自定义主题 | P2 | ⬜ | +| 性能监控面板 | P2 | ⬜ | + +--- + +## 五、开发规范 + +### 5.1 代码风格 + +```typescript +// ✅ 命名约定 +const MY_CONSTANT = 'value'; // 常量: SCREAMING_SNAKE_CASE +function getUserData() {} // 函数: camelCase +class GatewayManager {} // 类: PascalCase +interface ChannelConfig {} // 接口: PascalCase +type StatusType = 'running'; // 类型: PascalCase + +// ✅ 文件命名 +// 组件: PascalCase.tsx +// Dashboard.tsx, SkillCard.tsx + +// 工具/hooks: kebab-case.ts +// gateway-manager.ts, use-gateway.ts + +// ✅ 目录命名 +// kebab-case +// src/pages/skill-market/ + +// ✅ React 组件 +export function SkillCard({ skill, onSelect }: SkillCardProps) { + // hooks 在顶部 + const [loading, setLoading] = useState(false); + + // handlers + const handleClick = () => { /* ... */ }; + + // render + return ( + + {/* ... */} + + ); +} +``` + +### 5.2 Git 提交规范 + +``` +(): + + + +