feat: ClawPal v0.1 — Tauri desktop GUI for OpenClaw

4-page layout (Home, Recipes, Settings, Doctor) with sidebar nav
and integrated Chat panel powered by OpenClaw agent (--local).

- Home: status, agents overview, recommended recipes, recent activity
- Recipes: browse, preview diff, apply with params
- Settings: model profiles CRUD, chat model selection, provider catalog
- Doctor: diagnostics with auto-fix
- Chat: OpenClaw agent integration with session persistence,
  agent selector, read-only advisory context injection
- Progressive data loading to avoid UI blocking
- API key resolution from OpenClaw agent auth-profiles
- Model catalog from openclaw CLI with cache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhixian
2026-02-17 01:16:38 +09:00
commit 700c5e9ab2
53 changed files with 20292 additions and 0 deletions

47
docs/mvp-checklist.md Normal file
View File

@@ -0,0 +1,47 @@
# ClawPal MVP 验收清单
## 1. 安装向导
- [x] 打开 Recipes 列表
- [x] 选择一个 Recipe
- [x] 参数校验阻止非法输入
- [x] 点击 Preview 显示变更
- [x] 点击 Apply 成功写入并生成历史快照
## 2. 历史与回滚
- [x] 历史列表可见最近记录
- [x] 选中历史项可预览回滚 diff
- [x] 执行回滚后回填新快照(用于再次回滚)
- [x] 回滚后配置文件发生可见变化
## 3. Doctor
- [x] 运行 Doctor 返回至少一项问题(如有)
- [x] 对语法/字段问题展示修复建议
- [x] auto-fix 的问题可点击 fix状态刷新
- [x] 关键问题导致状态 score 下降
## 4. 可交付性
- [x] 无需网络也能完成核心流程
- [x] 目录存在于 `~/.openclaw`,历史文件落在 `.clawpal/history`
- [x] `npm run build` 成功
- [ ] `npm run release:dry-run` 输出通过项(无需执行发布)
## 5. 模型与频道管理v0.2
- [x] 模型 Profile 支持列表、创建、更新、删除
- [x] 全局模型绑定可设置与清空
- [x] Agent 模型覆盖可设置与清空
- [x] Channel 模型绑定可设置与清空
- [x] Channel 节点可更新 `type/mode/allowlist/model`
- [x] Channel 节点可安全删除
- [x] Recipes 支持外部文件/URL 源加载
## 6. Memory 与 Session 管理v0.2
- [x] Memory 文件列表可见
- [x] Memory 单文件删除与清空可用
- [x] Session 文件列表可见active + archive
- [x] Session 单文件删除与按 agent/全部清空可用

View File

@@ -0,0 +1,279 @@
# ClawPal MVP 设计文档(实现版)
日期2026-02-15
版本MVP-1.0
目标:用最小投入实现可用产品,覆盖 `design.md` 中 MVP 核心范围(安装向导、快照与回滚、配置诊断)。
## 1. 范围边界
### 1.1 本版实现范围MVP
- 安装向导
- Recipe 列表(内置静态 Recipes
- 参数 schema 校验必填、类型、pattern
- 预览变更diff
- 应用配置(含自动备份)
- 版本控制与回滚
- 每次写入前自动快照
- 历史记录列表
- 选中历史版本回滚(回滚前显示 diff
- 回滚过程二次确认
- 配置诊断Doctor
- JSON 解析
- 必填字段检查
- 端口占用(仅基础检查)
- 文件权限检查(读写)
- 一键修复建议(语法修复、默认值补齐)
### 1.2 明确不做
- 官网、用户系统、在线提交与评论
- 复杂的配置兼容迁移v1/v2
- 深度路径白名单策略(保留可配置白名单雏形)
- 插件化/市场化的远程 Recipe 发布
## 2. 目标架构Tauri 2 + React
### 2.1 分层
- `src-tauri/src`
- `main.rs`:初始化、窗口配置、命令注册
- `commands.rs`:所有跨端调用入口
- `recipe.rs`Recipe 定义、参数校验、模板渲染
- `config_io.rs`配置读写路径发现、json5 解析、备份文件读写)
- `history.rs`:快照目录与元数据管理
- `doctor.rs`:诊断与修复策略
- `src/`
- `pages/`
- `Home.tsx`:健康状态、版本、按钮入口
- `Recipes.tsx`:卡片列表与搜索过滤
- `Install.tsx`:安装向导
- `History.tsx`:历史快照
- `Doctor.tsx`:问题列表与修复动作
- `components/`
- `RecipeCard.tsx`
- `ParamForm.tsx`
- `DiffViewer.tsx`
- `StatusPill.tsx`
- `lib/`
- `recipe_catalog.ts`:内置 recipes内嵌 JSON 或 TS 常量)
- `api.ts`Tauri command 调用封装
- `state.ts``Context + useReducer` 统一状态与副作用
### 2.2 目录与文件
```
~/.openclaw/openclaw.json # 当前配置
~/.openclaw/.clawpal/ # 本地状态目录
~/.openclaw/.clawpal/history/
~/.openclaw/.clawpal/metadata.json # 快照元信息(列表可直接读)
```
## 3. Recipe 与配置模型
### 3.1 Recipe 核心结构
```ts
interface Recipe {
id: string;
name: string;
description: string;
version: string;
tags: string[];
difficulty: 'easy' | 'normal' | 'advanced';
params: RecipeParam[];
patchTemplate: string; // JSON Merge Patch with {{param}}
impact: {
category: 'low' | 'medium' | 'high';
summary: string;
};
}
interface RecipeParam {
id: string;
label: string;
type: 'string' | 'number' | 'boolean' | 'textarea';
required: boolean;
pattern?: string;
minLength?: number;
maxLength?: number;
placeholder?: string;
}
```
### 3.2 预览与应用结果
```ts
interface PreviewResult {
recipeId: string;
diff: string;
changes: ChangeItem[];
overwritesExisting: boolean;
canRollback: boolean; // true = 已生成快照
impactLevel: 'low' | 'medium' | 'high';
warnings: string[];
}
interface ChangeItem {
path: string;
op: 'add' | 'replace' | 'remove';
risk: 'low' | 'medium' | 'high';
reason?: string;
}
```
## 4. Tauri Command 设计
### 4.1 命令边界
- `get_system_status(): SystemStatus`
- `list_recipes(): Recipe[]`
- `preview_apply(recipe_id: string, params: Record<string, string>): PreviewResult`
- `apply_recipe(recipe_id: string, params: Record<string, string>): ApplyResult`
- `list_history(limit: number, offset: number): HistoryPage`
- `preview_rollback(snapshot_id: string): PreviewResult`
- `rollback(snapshot_id: string): ApplyResult`
- `run_doctor(): DoctorReport`
- `fix_issues(issue_ids: string[]): FixResult`
- `open_config_path(): string`
### 4.2 关键返回结构
```ts
interface ApplyResult {
ok: boolean;
snapshotId?: string; // 回滚锚点
configPath: string;
backupPath?: string;
warnings: string[];
errors?: string[];
}
interface DoctorReport {
ok: boolean;
issues: DoctorIssue[];
score: number; // 0-100 健康值
}
interface DoctorIssue {
id: string;
code: string;
severity: 'error' | 'warn' | 'info';
message: string;
autoFixable: boolean;
fixHint?: string;
}
```
## 5. 核心逻辑算法
### 5.1 参数校验
1. Recipe 找不到 -> 错误
2. 参数逐个校验:
- 必填
- 类型
- pattern正则
- 长度
3. 校验通过后进入渲染
### 5.2 参数渲染与差异生成
1.`{{param_id}}` 进行文本替换
2. 解析 base 配置与 patch 模板json5
3. 进行深合并merge patch
4. Diff 生成:
- 将当前配置与待应用配置序列化为 pretty JSON
- 输出 unified diff 或关键节点差异
### 5.3 应用流程Write path
1. 调用 preview
2. 创建快照:
- 将当前 `openclaw.json` 复制到 `.clawpal/history/<ts>_<slug>.json`
- 更新 `metadata.json`(按时间倒序)
3. 原子写新配置:
- 临时文件写入 -> rename 覆盖
4. 失败回滚:
- 临时文件清理
- 保留快照,但不上报成功
### 5.4 回滚流程
1. 读取目标快照
2. 计算与当前配置 diff
3. 确认后再执行快照(当前入历史)
4. 用目标快照替换当前配置
### 5.5 Doctor 与修复
- 语法错误:尝试修复尾逗号、未闭合引号等常见问题
- 关键字段缺失:按最小安全默认值补齐(仅在用户确认后)
- 端口占用:读取端口字段并做最小冲突提示(非阻塞 warning
- 权限问题:展示“文件不可读/不可写+路径来源”建议
## 6. 安全与约束MVP 简化)
- 禁止修改路径初版:
- `gateway.auth.*`
- `*.token`
- `*.apiKey`
- `dangerous` 字段提示:
- `gateway.port` 修改前增加二次确认
- Deep Link 与远程 Recipe 不支持MVP 不接入官网)
- 所有写操作必须先写快照,`apply_result.snapshotId` 作为审计锚点
## 7. 兼容与平台策略
- 路径优先级:
1. `~/.openclaw/.clawpal`(首次初始化)
2. 现有配置路径检测(若 `.openclaw` 已存在则复用)
3. WSL2 仅作“只读检测入口”MVP 只读展示,不做深度自动映射)
- Windows/macOS/Linux 使用 Tauri 提供的跨平台路径 API
## 8. 内置 RecipeMVP
1. Discord 频道专属人设
2. Telegram 群组配置
3. 定时任务配置
4. 模型切换
5. 性能优化
每个 Recipe 均含:
- id/name/version/params/patchTemplate
- default 示例值与最小校验规则
- impact 元信息(影响等级)
## 9. 里程碑与验收
### Milestone A第 1 阶段)
- 项目脚手架搭建
- Recipe 解析与参数校验
- 安装向导基础路径可跑通
- 最小 Diff 预览
- 验收:选 1 个 Recipe 完整完成 “填参数->预览->应用”
### Milestone B第 2 阶段)
- 快照元数据与历史列表
- 回滚预览与回滚执行
- 验收:应用后 1 步回滚成功且 current 状态可恢复
### Milestone C第 3 阶段)
- Doctor 基础扫描与修复入口
- 端口/权限/语法检查
- 验收:制造 1 处语法错误,修复后能读取启动
## 10. 关键风险与回退方案
- JSON5 风格保持未完全解决MVP 采用“标准化写回”,后续引入更精细 AST 修改
- 端口占用检测可能误报:提示“仅警告”不阻断
- Recipe 深入语义冲突:通过 change list 显示冲突路径并要求确认
- 若配置文件损坏到不可修复:保留备份、提示手工恢复和重建路径
## 11. 交付标准
- 所有命令返回可序列化错误码,不出现裸异常弹窗
- 每次 apply 成功都生成可回滚快照(除极端写入错误)
- 历史列表支持 `时间/来源/描述` 查看
- Doctor 能给出至少一条可执行修复动作
- 无法修复时给出建议与重试按钮

View File

@@ -0,0 +1,403 @@
# ClawPal MVP Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Deliver a working Tauri MVP for ClawPal that implements install wizard, snapshot/rollback, and doctor repair flows with data-safe write operations.
**Architecture:** A thin React UI orchestrates a Rust-backed command API in Tauri; all configuration reads/writes, preview/rollback logic, and diagnosis checks are implemented in Rust for deterministic behavior and simpler cross-platform filesystem handling.
**Tech Stack:** Tauri 2, Rust, React 18, TypeScript, json5, monaco-editor (diff), Vitest, Cargo test
---
### Task 1: 初始化项目骨架与基础命令骨架
**Files:**
- Create: `package.json`
- Create: `src-tauri/Cargo.toml`
- Create: `src-tauri/src/main.rs`
- Create: `src-tauri/src/commands.rs`
- Create: `src-tauri/src/lib.rs`
- Create: `src-tauri/src/commands_tests.rs`
- Create: `src/main.tsx`
- Create: `src/App.tsx`
- Create: `src-tauri/tauri.conf.json`
**Step 1: Write the failing test**
Create a Rust compile smoke test for command registration entry.
```rust
// src-tauri/src/commands_tests.rs
#[test]
fn test_register_commands_compiles() {
assert!(true);
}
```
**Step 2: Run it to verify it fails**
Run: `cd /Users/zhixian/Codes/clawpal/src-tauri && cargo test --test commands_tests -- --nocapture`
Expected: FAIL with missing crate/modules (project尚未初始化).
**Step 3: Write the minimal implementation**
- Initialize a valid Tauri app with `invoke_handler` placeholder.
- Register placeholder command stubs returning `Ok(())`.
**Step 4: Run test to verify it passes**
Run: `cd /Users/zhixian/Codes/clawpal/src-tauri && cargo test --test commands_tests -- --nocapture`
Expected: PASS.
**Step 5: Commit**
```bash
cd /Users/zhixian/Codes/clawpal
git add package.json src-tauri/Cargo.toml src-tauri/src/main.rs src-tauri/src/lib.rs src-tauri/src/commands.rs src-tauri/src/commands_tests.rs src-tauri/tauri.conf.json src/main.tsx src/App.tsx
git commit -m "chore: scaffold Tauri + React MVP project skeleton"
```
### Task 2: 建立类型与路径模型
**Files:**
- Create: `src-tauri/src/models.rs`
- Create: `src-tauri/src/config_io.rs`
- Modify: `src-tauri/src/lib.rs`
- Create: `src-tauri/src/models_tests.rs`
**Step 1: Write the failing test**
```rust
#[test]
fn test_openclaw_paths_are_resolved() {
let paths = resolve_openclaw_paths();
assert!(paths.config_path.to_string_lossy().contains(".openclaw"));
assert!(paths.history_dir.ends_with(".clawpal/history"));
}
```
**Step 2: Run it to verify it fails**
Run: `cargo test models_tests::test_openclaw_paths_are_resolved`
Expected: FAIL (path resolver未实现 / test compile fail).
**Step 3: Write the minimal implementation**
- Define `OpenClawPaths { config_path, history_dir, metadata_path }`.
- Implement path resolution from home directory via `dirs` crate and create dirs if missing (non-destructive).
**Step 4: Run test to verify it passes**
Run: `cargo test models_tests::test_openclaw_paths_are_resolved`
Expected: PASS.
**Step 5: Commit**
```bash
git add src-tauri/src/models.rs src-tauri/src/config_io.rs src-tauri/src/models_tests.rs src-tauri/src/lib.rs
git commit -m "feat: define core path model for openclaw config and history"
```
### Task 3: 实现 Recipe 类型与参数校验
**Files:**
- Create: `src-tauri/src/recipe.rs`
- Modify: `src-tauri/src/commands.rs`
- Create: `src/lib/recipe_catalog.ts`
- Create: `src-tauri/src/recipe_tests.rs`
**Step 1: Write the failing test**
```rust
#[test]
fn test_validate_recipe_params_missing_required() {
let recipe = sample_recipe();
let params = serde_json::json!({});
assert!(validate_recipe_params(&recipe, &params).is_err());
}
```
**Step 2: Run it to verify it fails**
Run: `cargo test recipe_tests::test_validate_recipe_params_missing_required`
Expected: FAIL (validate function未实现).
**Step 3: Write the minimal implementation**
- Implement `Recipe`, `RecipeParam`, `RecipeEngine::validate`.
- Parse params from static TS catalog entry and pass through Tauri command `list_recipes`.
**Step 4: Run test to verify it passes**
Run: `cargo test recipe_tests::test_validate_recipe_params_missing_required`
Expected: PASS.
**Step 5: Commit**
```bash
git add src-tauri/src/recipe.rs src-tauri/src/recipe_tests.rs src-tauri/src/commands.rs src/lib/recipe_catalog.ts
git commit -m "feat: define recipe schema and parameter validation"
```
### Task 4: 实现预览与应用流程(含备份)
**Files:**
- Modify: `src-tauri/src/recipe.rs`
- Modify: `src-tauri/src/config_io.rs`
- Create: `src-tauri/src/history.rs`
- Modify: `src-tauri/src/commands.rs`
- Create: `src-tauri/src/recipe_flow_tests.rs`
- Modify: `src/lib/api.ts`
- Create: `src/lib/types.ts`
**Step 1: Write the failing test**
```rust
#[test]
fn test_apply_writes_backup_before_modify() {
// Arrange temp openclaw path & in-memory json
// Assert backup exists after apply with different content hash
}
```
**Step 2: Run it to verify it fails**
Run: `cargo test recipe_flow_tests::test_apply_writes_backup_before_modify`
Expected: FAIL (apply + history not implemented).
**Step 3: Write the minimal implementation**
- Implement preview by rendering template + merge patch to candidate JSON.
- On apply: read current config, create snapshot copy, write temporary file then atomically replace target.
- Emit `ApplyResult` with `snapshot_id` and backup path.
**Step 4: Run test to verify it passes**
Run: `cargo test recipe_flow_tests::test_apply_writes_backup_before_modify`
Expected: PASS.
**Step 5: Commit**
```bash
git add src-tauri/src/recipe.rs src-tauri/src/config_io.rs src-tauri/src/history.rs src-tauri/src/commands.rs src-tauri/src/recipe_flow_tests.rs src/lib/api.ts src/lib/types.ts
git commit -m "feat: implement recipe preview/apply with backup snapshot"
```
### Task 5: 实现历史列表和回滚命令
**Files:**
- Modify: `src-tauri/src/history.rs`
- Modify: `src-tauri/src/commands.rs`
- Create: `src-tauri/src/history_tests.rs`
- Create: `src/pages/History.tsx`
- Modify: `src/App.tsx`
**Step 1: Write the failing test**
```rust
#[test]
fn test_history_snapshot_roundtrip_rollback() {
// create fake metadata + two snapshots
// restore earlier snapshot and verify current differs then matches target
}
```
**Step 2: Run it to verify it fails**
Run: `cargo test history_tests::test_history_snapshot_roundtrip_rollback`
Expected: FAIL (history index and rollback 未实现).
**Step 3: Write the minimal implementation**
- Implement metadata read/write format with monotonically sorted IDs.
- Implement `list_history`, `preview_rollback`, and `rollback`.
- Add history UI with “查看 diff / 回滚” actions.
**Step 4: Run test to verify it passes**
Run: `cargo test history_tests::test_history_snapshot_roundtrip_rollback`
Expected: PASS.
**Step 5: Commit**
```bash
git add src-tauri/src/history.rs src-tauri/src/commands.rs src-tauri/src/history_tests.rs src/pages/History.tsx src/App.tsx
git commit -m "feat: add snapshot history list and rollback flow"
```
### Task 6: 实现 Doctor 检查与修复
**Files:**
- Create: `src-tauri/src/doctor.rs`
- Modify: `src-tauri/src/commands.rs`
- Create: `src-tauri/src/doctor_tests.rs`
- Create: `src/pages/Doctor.tsx`
- Modify: `src/lib/api.ts`
**Step 1: Write the failing test**
```rust
#[test]
fn test_doctor_catches_invalid_json_syntax() {
// Provide broken json5 content
// assert issue with code = json.syntax
}
```
**Step 2: Run it to verify it fails**
Run: `cargo test doctor_tests::test_doctor_catches_invalid_json_syntax`
Expected: FAIL (doctor module未实现).
**Step 3: Write the minimal implementation**
- Check parse validity, mandatory top-level fields, permission/ownership basics.
- Implement one-shot fix functions for trailing comma + missing required field default.
- Return actionable `DoctorReport` from command.
**Step 4: Run test to verify it passes**
Run: `cargo test doctor_tests::test_doctor_catches_invalid_json_syntax`
Expected: PASS.
**Step 5: Commit**
```bash
git add src-tauri/src/doctor.rs src-tauri/src/commands.rs src-tauri/src/doctor_tests.rs src/pages/Doctor.tsx src/lib/api.ts
git commit -m "feat: add doctor diagnostics and safe auto-fix hooks"
```
### Task 7: 前端安装向导与 Diff 页面
**Files:**
- Modify: `src/pages/Recipes.tsx`
- Create: `src/pages/Install.tsx`
- Create: `src/components/RecipeCard.tsx`
- Create: `src/components/ParamForm.tsx`
- Create: `src/components/DiffViewer.tsx`
- Modify: `src/lib/api.ts`
- Create: `src/pages/Home.tsx`
**Step 1: Write the failing test**
```ts
// src/__tests__/InstallFlow.spec.ts
test('install flow shows preview and apply action', async () => {
render(<Install />);
// fill form -> click preview -> ensure diff rendered -> apply enabled
});
```
**Step 2: Run it to verify it fails**
Run: `npx vitest run src/__tests__/InstallFlow.spec.ts`
Expected: FAIL (页面未实现).
**Step 3: Write the minimal implementation**
- Wire form to Recipe params from catalog and invoke `preview_apply`.
- Add DiffViewer with highlight.
- Add confirm/apply button and success toasts.
**Step 4: Run test to verify it passes**
Run: `npx vitest run src/__tests__/InstallFlow.spec.ts`
Expected: PASS.
**Step 5: Commit**
```bash
git add src/pages/Recipes.tsx src/pages/Install.tsx src/components/RecipeCard.tsx src/components/ParamForm.tsx src/components/DiffViewer.tsx src/lib/api.ts src/pages/Home.tsx src/__tests__/InstallFlow.spec.ts
git commit -m "feat: implement recipe install wizard with preview+apply"
```
### Task 8: 主页面路由与状态整合
**Files:**
- Modify: `src/App.tsx`
- Modify: `src/lib/state.ts`
- Modify: `src/pages/Home.tsx`
- Modify: `src/pages/History.tsx`
- Create: `src/lib/state_tests.ts`
**Step 1: Write the failing test**
```ts
test('state loads recipes and history on boot', async () => {
// mock tauri commands, render App, assert key tiles are loaded
});
```
**Step 2: Run it to verify it fails**
Run: `npx vitest run src/lib/state_tests.ts`
Expected: FAIL (state orchestration未实现).
**Step 3: Write the minimal implementation**
- Implement shared reducer/events for system status, recipes, history, doctor report.
- Add tabs/routes for Home/Recipes/History/Doctor.
**Step 4: Run test to verify it passes**
Run: `npx vitest run src/lib/state_tests.ts`
Expected: PASS.
**Step 5: Commit**
```bash
git add src/App.tsx src/lib/state.ts src/lib/state_tests.ts src/pages/Home.tsx src/pages/History.tsx
git commit -m "feat: wire app-wide state and navigation"
```
### Task 9: 端到端验收与发布脚手架
**Files:**
- Modify: `README.md`
- Modify: `package.json`
- Create: `scripts/release.sh`
- Create: `docs/mvp-checklist.md`
- Modify: `src-tauri/tauri.conf.json`
**Step 1: Write the failing test**
```bash
./scripts/release.sh --dry-run
```
Expected: FAIL (脚本不存在).
**Step 2: Run it to verify it fails**
Run: `bash scripts/release.sh --dry-run`
Expected: FAIL / script not found.
**Step 3: Write the minimal implementation**
- Add build scripts and basic build verification matrix (macOS/Windows/Linux placeholders).
- Add manual QA checklist to validate install/rollback/doctor on clean and broken states.
- Update README for usage.
**Step 4: Run test to verify it passes**
Run: `bash scripts/release.sh --dry-run`
Expected: PASS (dry-run prints validated commands without publishing).
**Step 5: Commit**
```bash
git add README.md package.json scripts/release.sh docs/mvp-checklist.md src-tauri/tauri.conf.json
git commit -m "chore: add MVP runbook and release scripts"
```
---
## 执行方式
Plan complete and saved to `docs/plans/2026-02-15-clawpal-mvp-implementation-plan.md`. Two execution options:
1. Subagent-Driven (this session) - 我在当前会话按任务分派并在每步进行复核
2. Parallel Session (separate) - 开新会话并使用 executing-plans 执行,有里程碑检查点
Which approach?

View File

@@ -0,0 +1,260 @@
# ClawPal 产品精简 & 重新定位
> 从"全功能配置管理后台"回归"AI 配置助手"
## 1. 问题
v0.2 新增了 Models、Channels、Data 三个管理页面后,产品从"场景驱动的配置助手"滑向了"OpenClaw 全功能管理后台"。功能杂糅,偏离了核心用户(新手)的需求。
## 2. 新产品定位
**ClawPal = AI 配置助手 + 精选 Recipe 库**
- 核心用户:不想碰 JSON 的新手
- 第一入口Chat — 用自然语言描述需求LLM 生成配置方案
- 第二入口Recipe — 社区精选的常见场景,一键安装
- 安全保证所有变更Chat 或 Recipe走同一安全链路快照 → 预览 diff → 用户确认 → 应用
## 3. 精简前后对比
| 精简前 (7 页面) | 精简后 | 处理方式 |
|---|---|---|
| Home (状态仪表盘) | Home (状态 + Agents + 推荐 + Chat) | 重构 |
| Recipes | Recipes | 保留 |
| Install | Install (从 Recipe/Chat 进入) | 保留 |
| History | History | 保留 |
| Doctor | Doctor (+ 数据清理) | 扩展 |
| Models | Settings > Model Profiles | 降级,移除绑定功能 |
| Channels | 删除 | 由 Chat/Recipe 覆盖 |
| Data | 合并入 Doctor | 删除独立页面 |
## 4. 导航结构
左侧边栏4 个主入口 + 1 个设置:
```
┌──────────┬──────────────────────────────────┐
│ │ │
│ Home │ │
│ │ │
│ Recipes │ 页面内容 │
│ │ │
│ History │ │
│ │ │
│ Doctor │ │
│ │ │
│ ─────── │ │
│ Settings│ │
│ │ │
└──────────┴──────────────────────────────────┘
```
Install 不在侧边栏出现,从 Recipe 卡片或 Chat 触发后进入。
## 5. Home 页面
左右分栏布局:左侧主内容区,右侧常驻 Chat 窗口。
### 5.1 左侧主区域(四段式)
**状态摘要** — 一行紧凑卡片:
- 配置健康状态(✅/❌)
- OpenClaw 版本 + 是否有更新
- 当前默认模型
**Agents 概览** — 每个 Agent 一行:
- Agent 名称 / ID
- 当前使用的模型
- 关联的 ChannelsDiscord#xxx、Telegram 等)
- 在线状态
**推荐 Recipes** — 3-4 张卡片,点击进入安装向导
**最近操作** — 最近 3-5 条历史记录
### 5.2 右侧 Chat 窗口
常驻面板,使用用户在 Settings 中配置的 Model Profile 调用 LLM。
**LLM 可调用的工具集:**
| 工具 | 作用 | 安全级别 |
|------|------|----------|
| `read_config` | 读取当前配置 | 无风险(只读) |
| `list_agents` | 列出 Agent 信息 | 无风险 |
| `list_recipes` | 搜索/推荐 Recipe | 无风险 |
| `preview_change` | 生成配置补丁并展示 diff | 无风险(不写入) |
| `apply_change` | 应用配置变更 | 需用户点确认 |
| `run_doctor` | 运行诊断 | 无风险 |
| `generate_recipe` | 根据描述生成新 Recipe | 无风险(只生成定义) |
**交互流程:**
```
用户: "我想让 agent-2 在 Telegram 群里只回复被 @ 的消息"
LLM: 调用 read_config 了解当前配置
LLM: 生成配置补丁,调用 preview_change
Chat 内展示 diff 面板 + [确认应用] [取消] 按钮
用户点确认 → 自动快照 → 写入配置
```
**关键约束:**
- 所有写操作必须先展示 diff用户手动确认
- 使用用户在 Settings 中选定的 Chat 模型
- 未配置 Model Profile 时Chat 区域提示引导去 Settings
### 5.3 布局示意
```
┌──────────┬──────────────────────────┬──────────────────┐
│ │ │ │
│ Sidebar │ 状态摘要 (一行卡片) │ Chat 窗口 │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │健康 │ │版本 │ │模型 │ │ 💬 你想实现 │
│ │ │ ✅ │ │2.13│ │gpt4│ │ 什么配置? │
│ │ └────┘ └────┘ └────┘ │ │
│ │ │ ┌────────────┐ │
│ │ Agents │ │ 输入框 │ │
│ │ ┌──────────────────────┐ │ └────────────┘ │
│ │ │ agent-1 gpt-4o │ │ │
│ │ │ ✅ online Discord#1│ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ agent-2 claude │ │ │
│ │ │ ✅ online Telegram │ │ │
│ │ ├──────────────────────┤ │ │
│ │ │ agent-3 gpt-4o │ │ │
│ │ │ ⚠ no channel │ │ │
│ │ └──────────────────────┘ │ │
│ │ │ │
│ │ 推荐 Recipes │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐│ │
│ │ │人设 │ │模型 │ │性能 ││ │
│ │ └─────┘ └─────┘ └─────┘│ │
│ │ │ │
│ │ 最近操作 │ │
│ │ • 2/15 应用了"Discord人设"│ │
│ │ • 2/14 运行了 Doctor │ │
│ │ │ │
└──────────┴──────────────────────────┴──────────────────┘
```
## 6. Recipes 页面
保持现有设计:卡片式浏览,支持搜索/筛选/标签。
新增的 Recipe 类型(替代被删除的页面功能):
- "切换默认模型" — 替代 Models 页面的全局绑定
- "为 Agent 设置专属模型" — 替代 Models 页面的 Agent 绑定
- "配置 Discord 频道白名单" — 替代 Channels 页面
- "配置 Telegram mention 规则" — 替代 Channels 页面
## 7. Doctor 页面(扩展)
两个区域:
### 7.1 配置诊断(原有)
- JSON 语法检查
- 必填字段验证
- 端口占用检测
- 文件权限检查
- 一键修复 + 变更原因展示
### 7.2 数据清理(从 Data 合并)
- Memory 文件统计 + 一键清理
- Session 文件统计 + 按 Agent 清理 / 全部清理
- 磁盘占用展示
```
┌─────────────────────────────────────────┐
│ Doctor │
│ │
│ 配置诊断 [运行检查] │
│ ┌─────────────────────────────────┐ │
│ │ ✅ JSON 语法正确 │ │
│ │ ✅ 必填字段完整 │ │
│ │ ❌ 端口 8080 被占用 │ │
│ │ → [一键修复] 切换到 8081 │ │
│ └─────────────────────────────────┘ │
│ │
│ 数据清理 │
│ ┌─────────────────────────────────┐ │
│ │ Memory: 6 files (2.3 MB) │ │
│ │ [清理全部] │ │
│ │ Sessions: 23 files (15.1 MB) │ │
│ │ agent-1: 12 files (8.2 MB) │ │
│ │ agent-2: 11 files (6.9 MB) │ │
│ │ [按 Agent 清理] [全部] │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
## 8. Settings 页面
### 8.1 Model Profiles
首次启动自动从 OpenClaw 配置中提取,无需手动操作。
Profile 卡片展示字段:
- 名称
- 服务商provider
- 模型model
- API Key脱敏`sk-proj-abc...7xZ`
- 自定义地址(仅设置时显示)
```
┌─────────────────────────────────────┐
│ GPT-4o ✅ 启用 │
│ 服务商: openai │
│ 模型: gpt-4o │
│ API Key: sk-proj-abc...7xZ │
│ [编辑] [删除] │
├─────────────────────────────────────┤
│ Claude ✅ 启用 │
│ 服务商: anthropic │
│ 模型: claude-sonnet-4-5 │
│ API Key: sk-ant-k03...mNp │
│ 自定义地址: https://my-proxy.com/v1 │
│ [编辑] [删除] │
└─────────────────────────────────────┘
│ [+ 新建 Profile] │
```
### 8.2 Chat 模型选择
下拉选择 Chat 窗口使用的 Model Profile。
### 8.3 路径配置
OpenClaw 目录和 ClawPal 数据目录的显示与自定义。
## 9. 删除清单
以下代码在实施时需要删除或重构:
| 文件 | 处理 |
|------|------|
| `src/pages/Channels.tsx` | 删除 |
| `src/pages/Data.tsx` | 删除,功能迁移到 Doctor |
| `src/pages/Models.tsx` | 删除Profile 管理迁移到 Settings |
| `src-tauri/src/commands.rs` | 移除 Channel CRUD、Data 相关命令,保留 Model Profile 命令 |
| `src/App.tsx` | 移除 Channels/Data/Models 路由,改为侧边栏布局 |
## 10. 新增开发项
| 功能 | 优先级 | 说明 |
|------|--------|------|
| 侧边栏布局 | P0 | 替换当前顶部 Tab |
| Home 重构 | P0 | 四段式 + 右侧 Chat |
| Chat 窗口 | P0 | LLM 工具调用 + diff 确认流程 |
| Settings 页面 | P0 | Model Profile 管理 + Chat 模型选择 |
| Doctor 扩展 | P1 | 合并数据清理功能 |
| 新 Recipes | P1 | 模型切换、频道配置等替代 Recipe |
| Agents 概览 API | P1 | 首页 Agent 列表的后端支持 |
| 首次启动自动提取 | P2 | 自动从配置提取 Model Profiles |
---
*Created: 2026-02-16*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
# Model & Channel Management Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add practical in-app management for model auth profiles, model binding strategy, and basic channel configuration (dm/group/allowlist/mode) with safe write operations and Home status visibility.
**Architecture:** Keep existing Tauri command boundary as the source of truth. Rust keeps all file I/O + patch writes; UI only renders/edit values and submits commands. Configuration changes go through atomic write + snapshot, while model auth profile registry lives in ClawPal metadata directory for separation from OpenClaw config.
**Tech Stack:** Tauri 2, Rust (`serde`, `serde_json`), React 18, TypeScript.
---
### Task 1: Define shared data contract for model/channels
**Files:**
- `src/lib/types.ts`
- `src-tauri/src/commands.rs`
**Step 1: Write the structure in types + command models (code first pass)**
- Add frontend interfaces for:
- `ModelProfile` (`id`, `name`, `provider`, `model`, `authRef`, `baseUrl`, `description`, `enabled`)
- `ChannelNode` (`path`, `channelType`, `mode`, `allowlist`, `modelValue`, `hasModelField`)
- `ModelBinding` (`scope`, `scopeId`, `modelProfileId`, `modelValue`)
- API command input/output types.
**Step 2: Add corresponding Rust serializable structs in `commands.rs`**
- Add `#[derive(Serialize, Deserialize)]` structs that mirror above.
---
### Task 2: Backing storage + utilities
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/models.rs`
**Step 1: Add metadata paths for profile storage**
- Use `OpenClawPaths` with existing `clawpal_dir` and introduce a `model_profiles.json` file path inside it.
**Step 2: Implement helper readers/writers**
- `load_model_profiles(paths) -> Vec<ModelProfile>`
- `save_model_profiles(paths, &Vec<ModelProfile>)`
- Path-safe setters for nested JSON fields inside `openclaw.json` using dot-path nodes.
---
### Task 3: Model profile CRUD commands
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src/lib/api.ts`
- `src/lib/types.ts`
**Step 1: Add Tauri commands**
- `list_model_profiles() -> Vec<ModelProfile>`
- `upsert_model_profile(profile: ModelProfile) -> ModelProfile`
- `delete_model_profile(profile_id: String) -> bool`
**Step 2: Register commands in `invoke_handler`**
- Ensure frontend can call these commands via API wrapper.
---
### Task 4: Channel discovery and channel config commands
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src/lib/api.ts`
- `src/lib/types.ts`
**Step 1: Discover editable channel nodes**
- Implement recursion under `/channels` for nodes that carry `type|mode|allowlist|model`.
- Return normalized `ChannelNode` list with stable paths.
**Step 2: Expose write commands**
- `list_channels() -> Vec<ChannelNode>`
- `update_channel_config(path: String, channel_type: Option<String>, mode: Option<String>, allowlist: Vec<String>, model: Option<String>) -> bool`
- `delete_channel_node(path: String) -> bool` (safe: remove node if exists)
**Step 3: Register commands**
- Add new invoke entries in `lib.rs`.
---
### Task 5: Model binding commands (global/agent/channel)
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src/lib/api.ts`
- `src/lib/types.ts`
**Step 1: Implement read summary**
- Add helper to return effective model binding map from current config:
- global default model
- per-agent model overrides
- per-channel model overrides
**Step 2: Implement commands**
- `set_global_model(profile_id: Option<String>) -> bool`
- `set_agent_model(agent_id: String, profile_id: Option<String>) -> bool`
- `set_channel_model(channel_path: String, profile_id: Option<String>) -> bool`
**Step 3: Snapshot safety**
- Use existing snapshot flow (`read_openclaw_config` + `add_snapshot`) before each write.
---
### Task 6: Frontend pages and routing
**Files:**
- `src/App.tsx`
- `src/pages/Models.tsx` (new)
- `src/pages/Channels.tsx` (new)
- `src/lib/api.ts`
- `src/lib/types.ts`
**Step 1: Wire routes for Models and Channels**
- Extend top nav and render new pages.
**Step 2: Models page**
- List model profiles, create/update/delete quickly.
- Add `profile id` selector.
- Add global / agent / channel assignment panel (simple controls first).
**Step 3: Channels page**
- List discovered channel nodes and allow update model/allowlist/mode.
- Add minimal inline editing (textarea for allowlist lines).
---
### Task 7: Surface in Home
**Files:**
- `src-tauri/src/commands.rs`
- `src/lib/types.ts`
- `src/pages/Home.tsx`
**Step 1: Expand system status with `model` binding counts**
- Keep current aggregates and make counts update from registry + bindings.
**Step 2: Display quick links/button actions**
- From Home open Models / Channels quick nav (simple text action).
---
### Task 8: Manual verification checklist
**Files:**
- `docs/mvp-checklist.md`
**Step 1: Add verification items for this feature set**
- Profile add/update/delete.
- Channel mode/allowlist/model update.
- Global/agent/channel model assignment.
- Status card updates after edits.
**Step 2: Execute manual run list**
- Build frontend and verify command invocation completes.

View File

@@ -0,0 +1,109 @@
# Openclaw-Driven Model/Channel Ops Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
## For Feature
**Goal:** Enable model catalog and model/profile management to use openclaw CLI + caching keyed to openclaw version, add upgrade-check visibility, sync current config models into profiles, and enrich discord channel metadata.
**Architecture:** Introduce a small openclaw-command layer in Tauri that executes `openclaw` with `--json` options, normalizes JSON outputs, writes cache under clawpal dir keyed by openclaw CLI version, and exposes UI actions for sync/inspect.
**Tech Stack:** Rust (`std::process` + `serde_json`), React/Tauri IPC, OpenClaw CLI commands.
---
### Task 1: OpenClaw command adapter + version-aware model catalog cache
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/models.rs`
- `src-tauri/Cargo.toml` (if needed)
**Step 1: Implement command runner utilities**
- Add small wrappers for running CLI commands (`openclaw --version`, `openclaw models list --all --json`, with optional `--provider`, `openclaw channels list --json --no-usage`, `openclaw update status --json`, `openclaw channels resolve ... --json` when needed).
- Parse and normalize JSON outputs into internal structs.
- Capture stderr for diagnostic messages.
**Step 2: Add cache structs and paths**
- Add catalog cache file path e.g. `model-catalog-cache.json` under clawpal dir.
- Include fields: `cliVersion`, `updatedAt`, `providers`, `source`, `error`, `ttlMinutes`.
**Step 3: Add version-aware refresh policy**
- Read current `openclaw --version` and current cache version.
- If version changed, or cache stale (e.g. 12h), refresh by running `openclaw models list --all --json`.
- If refresh fails, fall back to config-based extraction so UI remains usable.
**Step 4: Wire `list_model_catalog` to new pipeline**
- Return CLI/ cache-normalized `ModelCatalogProvider[]`.
- Keep `model/probe` fallback from config providers in emergency path.
### Task 2: OpenClaw upgrade status endpoint
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src/lib/types.ts`
- `src/lib/api.ts`
- `src/pages/Home.tsx`
**Step 1: Add API result model**
- Add `OpenclawUpdateCheck` to `types.ts` with `installKind/channel/source/version/availability` etc (at least `outdated`, `currentVersion`, `latestVersion`, `upgradeAvailable`, `updateLine`, `checkedAt`).
**Step 2: Add backend command**
- Add `check_openclaw_update` that runs `openclaw update status --json`.
- Return normalized result and errors non-fatal to UI.
**Step 3: Extend status payload**
- Optionally add `openclawUpdate` field to `SystemStatus`, fetched inside `get_system_status`.
**Step 4: Show on Home page**
- Add “OpenClaw update” card: current vs latest, and availability warning if newer exists.
### Task 3: Auto-extract existing model bindings to profiles
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src/lib/types.ts`
- `src/lib/api.ts`
- `src/pages/Models.tsx`
**Step 1: Add extractor command**
- New command `extract_model_profiles_from_config(dryRun: bool)` that inspects `/agents/*model`, `channels.*.model`, and defaults for explicit model refs.
- For each unique model ref create/refresh profiles from current config, using auth mapping heuristics:
- if model has `provider` prefix and `auth` map exists, pick first matching `auth.profiles` for that provider;
- fallback to `default`.
- Return `created|updated|skipped` stats and preview list.
**Step 2: Frontend button action**
- Add button `Import current model config as profiles` in models page.
- Add optional checkbox / confirm to enable overwrite for detected matches.
**Step 3: Make bindings discover profile matches**
- Since extractor writes profiles with exact `provider/model`, channel/agent bindings immediately resolve to profile IDs.
- Keep `find_profile_by_model` logic unchanged (already matches by exact string).
### Task 4: Discord channel name enrichment via CLI resolve
**Files:**
- `src-tauri/src/commands.rs`
- `src-tauri/src/lib.rs`
- `src-tauri/src/types.ts` (if new shape)
- `src/pages/Channels.tsx`
**Step 1: Add channel-name resolver command**
- Parse Discord channel IDs from channel node paths (e.g. `channels.discord.guilds.<guildId>.channels.<channelId>`).
- For discovered IDs, call `openclaw channels resolve <id> --channel discord --kind group --json` and map id->name.
- Return best effort, with `displayName` and `nameStatus` (`resolved`/`missing`).
**Step 2: Cache small resolution results**
- Add small cache in clawpal dir keyed by `guildId:channelId` and openclaw version.
- Graceful fallback to raw ID when resolve fails.
**Step 3: UI display**
- Add `displayName` in channel row and keep path as stable key.
### Execution flow
**Use subagent-driven mode with review checkpoints** because this spans Rust + React + IPC.