diff --git a/README.en.md b/README.en.md index 8fb9a9a..0a4fe2b 100644 --- a/README.en.md +++ b/README.en.md @@ -1,5 +1,9 @@ # ClawScope +

+ ClawScope cover +

+ **Memory Made Visible, Evolution Enabled** · [中文](README.md) An OpenClaw memory and evolution management tool — a cross-platform desktop app built with Tauri 2 + Rust. @@ -20,6 +24,358 @@ npm install npm run tauri dev ``` +## OpenClaw Gateway Setup Guide + +ClawScope connects to OpenClaw through the OpenClaw Gateway. When troubleshooting, verify the Gateway bind mode first, then verify authentication. If TCP / WebSocket cannot connect, the token or password has not been checked yet. + +![OpenClaw Gateway binding and authentication flow](public/images/diagrams/openclaw-gateway-auth-binding.svg) + +### Config File And jq + +OpenClaw normally stores its config at: + +```text +~/.openclaw/openclaw.json +``` + +This guide uses `jq` to edit JSON. Prefer `jq --arg` for token and password values so examples do not hard-code secrets into the jq expression. + +Install `jq`: + +```bash +# macOS +brew install jq + +# Ubuntu / Debian +sudo apt-get update && sudo apt-get install -y jq + +# Windows +winget install jqlang.jq +# or +scoop install jq +``` + +Safe write pattern: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway.port = 18789' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +If the config does not yet contain a `gateway` object, initialize it first: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {})' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +### Gateway Config Shape + +A typical LAN-accessible config looks like this. Replace the redacted token with your own value. + +```json +{ + "gateway": { + "port": 18789, + "mode": "local", + "bind": "lan", + "auth": { + "mode": "token", + "token": "clawx-***REDACTED***" + } + } +} +``` + +### Bind Modes + +| Mode | Meaning | Effective Listen Address | Use Case | +|---|---|---|---| +| `loopback` | Localhost only | `127.0.0.1` | Local development and same-machine ClawScope | +| `lan` | LAN access | `0.0.0.0` | Multiple devices on the same LAN | +| `public` | Public-facing bind | `0.0.0.0` | Reverse proxy or public deployment with strong authentication | + +Set LAN binding, which exposes `0.0.0.0:18789`: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.bind = "lan" | .gateway.port = 18789 | .gateway.mode = "local"' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +Switch back to local-only binding: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.bind = "loopback"' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +Check the configured bind mode: + +```bash +jq '.gateway.bind' ~/.openclaw/openclaw.json +``` + +Check the actual listener: + +```bash +# macOS / Linux +lsof -nP -iTCP:18789 -sTCP:LISTEN + +# Windows PowerShell +Get-NetTCPConnection -LocalPort 18789 -State Listen +``` + +For LAN access, the listener should be `*:18789`, `0.0.0.0:18789`, or a LAN interface address, not only `127.0.0.1:18789`. + +### Authentication Modes + +| Mode | Config Field | Connection Shape | Use Case | +|---|---|---|---| +| `none` | No extra field | Direct connection | Local development or trusted private networks | +| `token` | `token` | `Authorization: Bearer ` | Multi-device access, API calls, LAN sharing | +| `password` | `password` | `X-OpenClaw-Password: ` | Simple manual entry | +| `trusted-proxy` | `trustedProxies` | Trust proxy source IPs | Reverse proxy deployments | + +### Token Auth + +Config example: + +```json +{ + "gateway": { + "port": 18789, + "auth": { + "mode": "token", + "token": "clawx-***REDACTED***" + } + } +} +``` + +Set token auth: + +```bash +config="$HOME/.openclaw/openclaw.json" +gateway_token="clawx-***REDACTED***" +tmp="$(mktemp)" +jq --arg token "$gateway_token" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "token", "token": $token}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +Connection header shape: + +```http +Authorization: Bearer clawx-***REDACTED*** +``` + +### Password Auth + +Config example: + +```json +{ + "gateway": { + "port": 18789, + "auth": { + "mode": "password", + "password": "***REDACTED***" + } + } +} +``` + +Set password auth: + +```bash +config="$HOME/.openclaw/openclaw.json" +gateway_password="***REDACTED***" +tmp="$(mktemp)" +jq --arg password "$gateway_password" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "password", "password": $password}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +Connection header shape: + +```http +X-OpenClaw-Password: ***REDACTED*** +``` + +Equivalent WebSocket connect message shape: + +```json +{ + "type": "connect", + "auth": { + "password": "***REDACTED***" + } +} +``` + +### No Auth And Trusted Proxy + +For same-machine or temporary trusted-network debugging: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "none"}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +For reverse proxy deployments, replace these example addresses with your proxy source IPs: + +```bash +config="$HOME/.openclaw/openclaw.json" +proxy_a="192.168.1.100" +proxy_b="10.0.0.1" +tmp="$(mktemp)" +jq --arg proxyA "$proxy_a" --arg proxyB "$proxy_b" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "trusted-proxy", "trustedProxies": [$proxyA, $proxyB]}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +### Switching Modes + +1. Update `gateway.bind` and `gateway.auth` in `~/.openclaw/openclaw.json`. +2. Restart OpenClaw Gateway. +3. In ClawScope, test `http://:18789` again. + +Restart examples: + +```bash +# Foreground process: stop the old process with Ctrl+C, then run: +openclaw gateway + +# If your environment uses openclaw-gateway as the process name: +pkill -f openclaw-gateway +sleep 2 +openclaw gateway > /tmp/openclaw-gateway.log 2>&1 & +``` + +### Quick Switch Script + +The script below uses `jq --arg` and redacted placeholders. + +```bash +#!/usr/bin/env bash +set -euo pipefail + +OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$HOME/.openclaw/openclaw.json}" + +write_gateway_config() { + local bind="$1" + local auth_mode="$2" + local auth_value="${3:-}" + local tmp + tmp="$(mktemp)" + + case "$auth_mode" in + token) + jq --arg bind "$bind" --arg token "$auth_value" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "token", "token": $token} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + password) + jq --arg bind "$bind" --arg password "$auth_value" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "password", "password": $password} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + none) + jq --arg bind "$bind" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "none"} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + *) + echo "unsupported auth mode: $auth_mode" >&2 + rm -f "$tmp" + return 2 + ;; + esac + + mv "$tmp" "$OPENCLAW_CONFIG" +} + +restart_gateway() { + pkill -f openclaw-gateway || true + sleep 2 + openclaw gateway > /tmp/openclaw-gateway.log 2>&1 & +} + +show_gateway_status() { + echo "=== Gateway config ===" + jq '.gateway' "$OPENCLAW_CONFIG" + echo + echo "=== Non-loopback IPv4 addresses ===" + if command -v ip >/dev/null 2>&1; then + ip -4 addr show | grep -oE 'inet [0-9.]+' | awk '{print $2}' | grep -v '^127\.' + else + ifconfig | grep "inet " | grep -v 127.0.0.1 + fi +} + +# Examples: +# write_gateway_config "lan" "password" "***REDACTED***" && restart_gateway && show_gateway_status +# write_gateway_config "lan" "token" "clawx-***REDACTED***" && restart_gateway && show_gateway_status +# write_gateway_config "loopback" "none" && restart_gateway && show_gateway_status +``` + +### Viewing Current Config Safely + +Show the full Gateway config: + +```bash +jq '.gateway' ~/.openclaw/openclaw.json +``` + +Show auth config with secrets masked: + +```bash +jq ' + .gateway.auth + | if .token then .token = "***REDACTED***" else . end + | if .password then .password = "***REDACTED***" else . end +' ~/.openclaw/openclaw.json +``` + +LAN connection summary template: + +| Item | Example | +|---|---| +| Host IP | `192.168.1.xxx` | +| Gateway URL | `http://192.168.1.xxx:18789` | +| Listen Address | `0.0.0.0:18789` | +| Auth Mode | `token` or `password` | +| Credential | `***REDACTED***` | + +### Common Config Combinations + +| Scenario | `bind` | `auth.mode` | Notes | +|---|---|---|---| +| Local debug | `loopback` | `none` | Easiest same-machine setup | +| Local secure | `loopback` | `token` | Same-machine access with token | +| LAN sharing | `lan` | `password` | Good for a small number of manually configured devices | +| LAN API | `lan` | `token` | Better for multiple devices or automation | +| Public deployment | `public` | `token` | Requires a strong token, reverse proxy, and extra network controls | + +### Troubleshooting Order + +1. Confirm `openclaw gateway` is running on the OpenClaw host. +2. Confirm `gateway.bind` is `lan` and the actual listener is not only `127.0.0.1`. +3. From the ClawScope machine, run `Test-NetConnection -Port 18789` or `nc -vz 18789`. +4. Only after TCP works, check whether token / password matches the host config. +5. If you see `Handshake not finished`, confirm the target port is really the OpenClaw Gateway WebSocket service and not a proxy, stale process, or another service. + ## Build ```bash @@ -108,12 +464,9 @@ For additional details and the release matrix, see: ## Documentation -If you use the BMAD workflow locally, planning artifacts reside in `_bmad-output/` (not tracked by `.gitignore` in this repo; clone and generate or obtain from your team). - -- PRD: `_bmad-output/planning-artifacts/main/prd.md` - Project setup: `_bmad-output/planning-artifacts/main/PROJECT_SETUP.md` - Help: [`docs/help/choose-extra-paths-or-knowledge-injection.md`](docs/help/choose-extra-paths-or-knowledge-injection.md) ## License -[MIT](LICENSE) \ No newline at end of file +[MIT](LICENSE) diff --git a/README.md b/README.md index 04dc6eb..5b27cfa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # ClawScope +

+ ClawScope 封面 +

+ **记忆可见,进化可期** · [English](README.en.md) OpenClaw 记忆与进化管理工具 — 基于 Tauri 2 + Rust 构建的跨平台桌面应用。 @@ -13,6 +17,12 @@ OpenClaw 记忆与进化管理工具 — 基于 Tauri 2 + Rust 构建的跨平 - **配置管理** — 集中管理 OpenClaw 节点配置与状态 - **跨平台** — Windows / macOS / Linux 原生桌面应用 +## 架构一览 + +ClawScope 是一个本地优先的桌面控制面:React 负责记忆、配置、进化等工作视图,Tauri IPC 将界面动作转发到 Rust 命令层,Rust 侧再通过 OpenClaw Gateway 的 WebSocket 协议访问 Agent、记忆、配置与进化目标。本地 JSON 存储只保存连接身份、端点、审计历史与快照,用于重连、追踪和回滚。 + +![ClawScope 系统架构图](public/images/diagrams/clawscope-system-architecture.svg) + ## 快速开始 ```bash @@ -20,6 +30,358 @@ npm install npm run tauri dev ``` +## OpenClaw Gateway 设置指南 + +ClawScope 通过 OpenClaw Gateway 访问记忆、进化和 Agent 配置。排查连接问题时先确认 Gateway 监听范围,再确认认证模式;如果 TCP / WebSocket 都没有连通,token 或 password 不会被校验。 + +![OpenClaw Gateway 绑定与认证配置路径](public/images/diagrams/openclaw-gateway-auth-binding.svg) + +### 配置文件与 jq + +OpenClaw 配置文件默认位于: + +```text +~/.openclaw/openclaw.json +``` + +本指南使用 `jq` 修改 JSON 配置。推荐使用 `--arg` 传入 token、password、bind 等变量,避免把实际凭据直接拼进 jq 表达式。 + +安装 `jq`: + +```bash +# macOS +brew install jq + +# Ubuntu / Debian +sudo apt-get update && sudo apt-get install -y jq + +# Windows +winget install jqlang.jq +# 或 +scoop install jq +``` + +安全写回配置的通用写法: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway.port = 18789' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +如果当前配置缺少 `gateway` 节点,先初始化最小结构: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {})' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +### Gateway 配置结构 + +局域网访问的典型配置如下。示例中的 token 已脱敏,请替换为你自己的值。 + +```json +{ + "gateway": { + "port": 18789, + "mode": "local", + "bind": "lan", + "auth": { + "mode": "token", + "token": "clawx-***REDACTED***" + } + } +} +``` + +### 绑定模式 + +| 模式 | 说明 | 实际监听地址 | 适用场景 | +|---|---|---|---| +| `loopback` | 仅本机访问 | `127.0.0.1` | 本机开发、本机 ClawScope | +| `lan` | 局域网访问 | `0.0.0.0` | 同一局域网内多设备连接 | +| `public` | 对公网地址开放 | `0.0.0.0` | 反向代理或公网部署,必须配合强认证与网络边界 | + +设置局域网监听,也就是让 Gateway 监听 `0.0.0.0:18789`: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.bind = "lan" | .gateway.port = 18789 | .gateway.mode = "local"' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +切回仅本机监听: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.bind = "loopback"' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +查看当前绑定模式: + +```bash +jq '.gateway.bind' ~/.openclaw/openclaw.json +``` + +确认进程实际监听地址: + +```bash +# macOS / Linux +lsof -nP -iTCP:18789 -sTCP:LISTEN + +# Windows PowerShell +Get-NetTCPConnection -LocalPort 18789 -State Listen +``` + +如果局域网访问正常,服务端应监听在 `*:18789`、`0.0.0.0:18789` 或对应网卡地址,而不是只监听 `127.0.0.1:18789`。 + +### 认证模式对比 + +| 模式 | 配置字段 | 连接方式 | 适用场景 | +|---|---|---|---| +| `none` | 无额外字段 | 直接连接 | 本地开发或可信内网 | +| `token` | `token` | `Authorization: Bearer ` | 多设备连接、API 调用、局域网共享 | +| `password` | `password` | `X-OpenClaw-Password: ` | 简单场景、人工输入 | +| `trusted-proxy` | `trustedProxies` | 信任代理来源 IP | 反向代理部署 | + +### Token 认证 + +配置示例: + +```json +{ + "gateway": { + "port": 18789, + "auth": { + "mode": "token", + "token": "clawx-***REDACTED***" + } + } +} +``` + +设置命令: + +```bash +config="$HOME/.openclaw/openclaw.json" +gateway_token="clawx-***REDACTED***" +tmp="$(mktemp)" +jq --arg token "$gateway_token" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "token", "token": $token}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +连接侧含义: + +```http +Authorization: Bearer clawx-***REDACTED*** +``` + +### Password 认证 + +配置示例: + +```json +{ + "gateway": { + "port": 18789, + "auth": { + "mode": "password", + "password": "***REDACTED***" + } + } +} +``` + +设置命令: + +```bash +config="$HOME/.openclaw/openclaw.json" +gateway_password="***REDACTED***" +tmp="$(mktemp)" +jq --arg password "$gateway_password" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "password", "password": $password}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +连接侧含义: + +```http +X-OpenClaw-Password: ***REDACTED*** +``` + +WebSocket connect 消息中的等价含义: + +```json +{ + "type": "connect", + "auth": { + "password": "***REDACTED***" + } +} +``` + +### 无认证与 Trusted Proxy + +仅本机或可信内网临时调试可使用无认证: + +```bash +config="$HOME/.openclaw/openclaw.json" +tmp="$(mktemp)" +jq '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "none"}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +反向代理场景可使用 trusted proxy。示例 IP 请替换为你的代理出口地址: + +```bash +config="$HOME/.openclaw/openclaw.json" +proxy_a="192.168.1.100" +proxy_b="10.0.0.1" +tmp="$(mktemp)" +jq --arg proxyA "$proxy_a" --arg proxyB "$proxy_b" '.gateway = (.gateway // {}) | .gateway.auth = {"mode": "trusted-proxy", "trustedProxies": [$proxyA, $proxyB]}' "$config" > "$tmp" && mv "$tmp" "$config" +``` + +### 模式切换步骤 + +1. 修改 `~/.openclaw/openclaw.json` 中的 `gateway.bind` 和 `gateway.auth`。 +2. 重启 OpenClaw Gateway,使配置生效。 +3. 在 ClawScope 中使用 `http://:18789` 重新测试连接。 + +重启示例: + +```bash +# 如果 Gateway 是前台进程,先在原终端按 Ctrl+C 停止,再启动: +openclaw gateway + +# 如果确认当前环境使用 openclaw-gateway 作为进程名,可使用: +pkill -f openclaw-gateway +sleep 2 +openclaw gateway > /tmp/openclaw-gateway.log 2>&1 & +``` + +### 快速切换脚本 + +下面脚本使用 `jq --arg` 写入配置,并在写入后重启 Gateway。示例值均为脱敏占位符。 + +```bash +#!/usr/bin/env bash +set -euo pipefail + +OPENCLAW_CONFIG="${OPENCLAW_CONFIG:-$HOME/.openclaw/openclaw.json}" + +write_gateway_config() { + local bind="$1" + local auth_mode="$2" + local auth_value="${3:-}" + local tmp + tmp="$(mktemp)" + + case "$auth_mode" in + token) + jq --arg bind "$bind" --arg token "$auth_value" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "token", "token": $token} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + password) + jq --arg bind "$bind" --arg password "$auth_value" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "password", "password": $password} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + none) + jq --arg bind "$bind" ' + .gateway = (.gateway // {}) + | .gateway.mode = "local" + | .gateway.port = 18789 + | .gateway.bind = $bind + | .gateway.auth = {"mode": "none"} + ' "$OPENCLAW_CONFIG" > "$tmp" + ;; + *) + echo "unsupported auth mode: $auth_mode" >&2 + rm -f "$tmp" + return 2 + ;; + esac + + mv "$tmp" "$OPENCLAW_CONFIG" +} + +restart_gateway() { + pkill -f openclaw-gateway || true + sleep 2 + openclaw gateway > /tmp/openclaw-gateway.log 2>&1 & +} + +show_gateway_status() { + echo "=== Gateway 配置 ===" + jq '.gateway' "$OPENCLAW_CONFIG" + echo + echo "=== 本机非 loopback IPv4 ===" + if command -v ip >/dev/null 2>&1; then + ip -4 addr show | grep -oE 'inet [0-9.]+' | awk '{print $2}' | grep -v '^127\.' + else + ifconfig | grep "inet " | grep -v 127.0.0.1 + fi +} + +# 使用示例: +# write_gateway_config "lan" "password" "***REDACTED***" && restart_gateway && show_gateway_status +# write_gateway_config "lan" "token" "clawx-***REDACTED***" && restart_gateway && show_gateway_status +# write_gateway_config "loopback" "none" && restart_gateway && show_gateway_status +``` + +### 当前配置查看 + +查看完整 Gateway 配置: + +```bash +jq '.gateway' ~/.openclaw/openclaw.json +``` + +查看认证配置,避免在截图或日志中泄露真实凭据: + +```bash +jq ' + .gateway.auth + | if .token then .token = "***REDACTED***" else . end + | if .password then .password = "***REDACTED***" else . end +' ~/.openclaw/openclaw.json +``` + +局域网连接信息汇总模板: + +| 项目 | 示例值 | +|---|---| +| 本机 IP | `192.168.1.xxx` | +| Gateway URL | `http://192.168.1.xxx:18789` | +| 监听地址 | `0.0.0.0:18789` | +| 认证方式 | `token` 或 `password` | +| 凭据 | `***REDACTED***` | + +### 常见配置组合 + +| 场景 | `bind` | `auth.mode` | 说明 | +|---|---|---|---| +| 本机调试 | `loopback` | `none` | 最简单,仅本机无认证 | +| 本机安全 | `loopback` | `token` | 仅本机访问,但仍要求 token | +| 局域网共享 | `lan` | `password` | 适合少量人工输入的设备 | +| 局域网 API | `lan` | `token` | 适合多设备、自动化或 API 调用 | +| 公网部署 | `public` | `token` | 需要强 token、反向代理与额外网络边界 | + +### 连接排查顺序 + +1. 在 OpenClaw 主机上确认 `openclaw gateway` 已启动。 +2. 确认 `gateway.bind` 是 `lan`,并且实际监听不是只在 `127.0.0.1`。 +3. 在 ClawScope 所在机器上执行 `Test-NetConnection -Port 18789` 或 `nc -vz 18789`。 +4. TCP 通了之后,再检查 token / password 是否和服务端配置一致。 +5. 如果报 `Handshake not finished`,优先确认目标端口是否真的是 OpenClaw Gateway WebSocket 服务,而不是被代理、旧进程或其他服务占用。 + ## 构建 ```bash @@ -108,12 +470,9 @@ npm run visual:ci ## 相关文档 -若本地使用 BMAD 工作流,规划产物在 `_bmad-output/`(本仓库 `.gitignore` 不跟踪;克隆后需自行生成或从团队渠道获取)。 - -- PRD: `_bmad-output/planning-artifacts/main/prd.md` - 项目设置: `_bmad-output/planning-artifacts/main/PROJECT_SETUP.md` - 帮助文档: [`docs/help/choose-extra-paths-or-knowledge-injection.md`](docs/help/choose-extra-paths-or-knowledge-injection.md) ## License -[MIT](LICENSE) \ No newline at end of file +[MIT](LICENSE) diff --git a/package-lock.json b/package-lock.json index ef50be9..1a8fe6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claw-scope", - "version": "0.1.4", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claw-scope", - "version": "0.1.4", + "version": "0.1.6", "dependencies": { "@radix-ui/react-accordion": "1.2.3", "@radix-ui/react-alert-dialog": "1.1.6", diff --git a/package.json b/package.json index 26bad66..9127eae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "claw-scope", "private": true, - "version": "0.1.4", + "version": "0.1.6", "type": "module", "scripts": { "dev": "vite", diff --git a/public/images/covers/clawscope-cover-v3-animated-en.webp b/public/images/covers/clawscope-cover-v3-animated-en.webp new file mode 100644 index 0000000..9cb2691 Binary files /dev/null and b/public/images/covers/clawscope-cover-v3-animated-en.webp differ diff --git a/public/images/covers/clawscope-cover-v3-animated.webp b/public/images/covers/clawscope-cover-v3-animated.webp new file mode 100644 index 0000000..cd11fb9 Binary files /dev/null and b/public/images/covers/clawscope-cover-v3-animated.webp differ diff --git a/public/images/diagrams/clawscope-system-architecture.svg b/public/images/diagrams/clawscope-system-architecture.svg new file mode 100644 index 0000000..577b7c5 --- /dev/null +++ b/public/images/diagrams/clawscope-system-architecture.svg @@ -0,0 +1,190 @@ + + ClawScope 系统架构图 + ClawScope 由 React 视图层、Tauri 命令桥、Rust 本地能力、OpenClaw Gateway 与本地 JSON 状态存储组成。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ClawScope System Architecture + OpenClaw 记忆可视化、配置管理与进化审计的本地桌面控制面 + + + + + + + + + + + + + + + + + + + + ClawScope Desktop App / Tauri 2 runtime + + + React UI Layer + + + Rust Core Layer + + + External OpenClaw Runtime + + + + Operator + 搜索 / 审计 / 配置 + 桌面工作台 + + + + Shell + Router + Profile / Memory / Config + Evolution views + + + OpenClawContext + 连接状态 / 心跳 / 会话列表 + LAN discovery / saved endpoints + 统一前端命令入口 + + + Domain Panels + 记忆搜索 / 知识图谱 / 足迹 + Agent 设置 / 配置 schema + 进化预览与审计报告 + + + Browser localStorage + URL / auth mode / setup flags + + + + Tauri IPC + invoke(...) + gateway_* + evolution_* + + + + Gateway Orchestrator + commands + connector + WebSocket RPC / methods + + + Session State + active sessions / snapshots + pending request router + + + Auth + Discovery + device identity / signer + challenge / token fallback + + + Evolution Engine + preview / execute / rollback + + + + Gateway Store + identity / tokens / endpoints + + + Evolution Store + history / audit / snapshots + + + + OpenClaw Gateway + ws://host:18789 + protocol verified endpoint + + + Agent Registry + agents / identity / workspace + + + Memory Runtime + MEMORY.md / daily memory + semantic search / timeline + index + diagnostics + + + Config Surface + agent settings / config schema + + + + UI + views call context + commands + WebSocket RPC + challenge + signed connect + memory get/set/index + 本地 JSON 状态保障离线重连、审计与回滚 + + + Legend + + Frontend + + Rust backend + + Local store + + Security/auth + + IPC/runtime boundary + + Auth flow + + Data/control flow + + Source: README architecture overview, src/app/contexts/OpenClawContext.tsx, src-tauri/src/lib.rs, gateway/*, evolution/* + diff --git a/public/images/diagrams/openclaw-gateway-auth-binding.svg b/public/images/diagrams/openclaw-gateway-auth-binding.svg new file mode 100644 index 0000000..4c7d132 --- /dev/null +++ b/public/images/diagrams/openclaw-gateway-auth-binding.svg @@ -0,0 +1,66 @@ + + OpenClaw Gateway binding and authentication flow + A client reaches OpenClaw Gateway through a LAN URL, the Gateway listens on a bind mode, then applies token, password, none, or trusted proxy authentication before exposing OpenClaw runtime access. + + + + + + + + + + + + + + + + OpenClaw Gateway 配置路径 + 先确认监听范围,再选择认证方式;ClawScope 只在网络与 WebSocket 握手成功后才进入 token / password 验证。 + + + + ClawScope 客户端 + http://LAN-IP:18789 + 测试 TCP / WebSocket + + + + + Gateway 监听层 + + bind = lan + 0.0.0.0:18789 + loopback 仅本机可访问 + + + + + 认证层 + + token + + password + + none + + + + + OpenClaw Runtime + 记忆、进化、Agent 配置与文件访问都在认证通过后开放。 + + + + + + + + + 切换时的最小闭环 + 1. jq 修改 openclaw.json + 2. 重启 Gateway + 3. ClawScope 重新测试连接 + + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index cc01ec5..c57720b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -322,7 +322,7 @@ dependencies = [ [[package]] name = "claw-scope" -version = "0.1.3" +version = "0.1.6" dependencies = [ "base64 0.22.1", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0753b88..423227e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "claw-scope" -version = "0.1.4" +version = "0.1.6" description = "ClawScope - 记忆可见,进化可期" authors = ["milome"] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c403f77..c4701e9 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "ClawScope", - "version": "0.1.4", + "version": "0.1.6", "identifier": "com.claw.scope", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/app/components/setup/AgentSettingsDefaultRoutingCard.tsx b/src/app/components/setup/AgentSettingsDefaultRoutingCard.tsx index 9ce0e99..ea45472 100644 --- a/src/app/components/setup/AgentSettingsDefaultRoutingCard.tsx +++ b/src/app/components/setup/AgentSettingsDefaultRoutingCard.tsx @@ -32,7 +32,8 @@ export function AgentSettingsDefaultRoutingCard({ checked={isDefaultDraft} disabled={!canEdit} onChange={(event) => onChange(event.target.checked)} - className="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500" + style={{ accentColor: "#d97706" }} + className="h-4 w-4 rounded border-slate-300 text-amber-600 focus:ring-amber-500" />
diff --git a/src/app/components/setup/AgentSettingsFieldMetadataSummary.tsx b/src/app/components/setup/AgentSettingsFieldMetadataSummary.tsx index 82c7cb8..5b544a9 100644 --- a/src/app/components/setup/AgentSettingsFieldMetadataSummary.tsx +++ b/src/app/components/setup/AgentSettingsFieldMetadataSummary.tsx @@ -68,36 +68,54 @@ export function AgentSettingsFieldMetadataSummary({ return null; } + const source = sourceLabel(metadata.source, t); + return ( -
-
-
- {t("config.agentSettings.meta.sourceLabel")} -
-
- {sourceLabel(metadata.source, t)} -
- {metadata.path ? ( -
- {metadata.path} -
- ) : null} -
- {metadata.writeActions.map((action) => ( +
+ + + {t("config.agentSettings.meta.sourceLabel")}: {source} + + + {t( + "config.agentSettings.meta.writeCount", + metadata.writeActions.length, + )} + + + {t("config.agentSettings.meta.details")} + + +
- {t("config.agentSettings.meta.writeLabel")} + {t("config.agentSettings.meta.sourceLabel")}
-
- {writeActionLabel(action, t)} +
+ {source}
+ {metadata.path ? ( +
+ {metadata.path} +
+ ) : null}
- ))} -
+ {metadata.writeActions.map((action) => ( +
+
+ {t("config.agentSettings.meta.writeLabel")} +
+
+ {writeActionLabel(action, t)} +
+
+ ))} +
+
); } diff --git a/src/app/components/setup/AgentSettingsModule.tsx b/src/app/components/setup/AgentSettingsModule.tsx index 690f15e..80b28d3 100644 --- a/src/app/components/setup/AgentSettingsModule.tsx +++ b/src/app/components/setup/AgentSettingsModule.tsx @@ -399,52 +399,240 @@ function ScopeFieldCard({ ); } +function ScopeSection({ + title, + description, + children, +}: { + title: string; + description: string; + children: ReactNode; +}) { + return ( +
+
+

+ {title} +

+

+ {description} +

+
+ {children} +
+ ); +} + +function OverrideFieldRow({ + step, + icon, + title, + hint, + metadata, + schema, + loading = false, + children, +}: { + step: string; + icon: ReactNode; + title: string; + hint: string; + metadata?: GatewayAgentSettingsFieldMetadata | null; + schema?: GatewayConfigSchemaLookupResult | null; + loading?: boolean; + children: ReactNode; +}) { + const tone = metadata?.source === "universal_defaults" ? "violet" : "emerald"; + const toneClasses = + tone === "violet" + ? { + step: + "bg-violet-500 text-white shadow-violet-500/25 dark:bg-violet-400 dark:text-slate-950", + icon: + "bg-violet-100 text-violet-700 dark:bg-violet-950/70 dark:text-violet-300", + headingClassName: "text-violet-700 dark:text-violet-300", + shell: + "border-violet-200/60 bg-white/80 dark:border-violet-900/40 dark:bg-slate-950/45", + } + : { + step: + "bg-emerald-500 text-white shadow-emerald-500/25 dark:bg-emerald-400 dark:text-slate-950", + icon: + "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300", + headingClassName: "text-emerald-700 dark:text-emerald-300", + shell: + "border-emerald-200/60 bg-white/80 dark:border-emerald-900/40 dark:bg-slate-950/45", + }; + + return ( +
+
+
+ {step} +
+
+
+ {icon} +
+
+ {title} +
+

+ {hint} +

+ {schema ? ( +
+ +
+ ) : null} + +
+
+
+ {children} +
+
+ ); +} + +const MEMORY_SEARCH_SECTION_TONES = { + sky: { + shell: + "border-sky-100 bg-gradient-to-br from-sky-50/95 via-white/80 to-white/70 dark:border-sky-950/60 dark:from-sky-950/30 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-sky-500 text-white shadow-sky-500/25 dark:bg-sky-400 dark:text-slate-950", + icon: "bg-sky-100 text-sky-700 dark:bg-sky-950/70 dark:text-sky-300", + }, + cyan: { + shell: + "border-cyan-100 bg-gradient-to-br from-cyan-50/95 via-white/80 to-white/70 dark:border-cyan-950/60 dark:from-cyan-950/30 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-cyan-500 text-white shadow-cyan-500/25 dark:bg-cyan-400 dark:text-slate-950", + icon: "bg-cyan-100 text-cyan-700 dark:bg-cyan-950/70 dark:text-cyan-300", + }, + emerald: { + shell: + "border-emerald-100 bg-gradient-to-br from-emerald-50/95 via-white/80 to-white/70 dark:border-emerald-950/60 dark:from-emerald-950/25 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-emerald-500 text-white shadow-emerald-500/25 dark:bg-emerald-400 dark:text-slate-950", + icon: + "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300", + }, + amber: { + shell: + "border-amber-100 bg-gradient-to-br from-amber-50/95 via-white/80 to-white/70 dark:border-amber-950/60 dark:from-amber-950/25 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-amber-500 text-white shadow-amber-500/25 dark:bg-amber-400 dark:text-slate-950", + icon: "bg-amber-100 text-amber-700 dark:bg-amber-950/70 dark:text-amber-300", + }, + rose: { + shell: + "border-rose-100 bg-gradient-to-br from-rose-50/95 via-white/80 to-white/70 dark:border-rose-950/60 dark:from-rose-950/25 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-rose-500 text-white shadow-rose-500/25 dark:bg-rose-400 dark:text-slate-950", + icon: "bg-rose-100 text-rose-700 dark:bg-rose-950/70 dark:text-rose-300", + }, + violet: { + shell: + "border-violet-100 bg-gradient-to-br from-violet-50/95 via-white/80 to-white/70 dark:border-violet-950/60 dark:from-violet-950/30 dark:via-slate-950/80 dark:to-slate-950/60", + marker: + "bg-violet-500 text-white shadow-violet-500/25 dark:bg-violet-400 dark:text-slate-950", + icon: + "bg-violet-100 text-violet-700 dark:bg-violet-950/70 dark:text-violet-300", + }, +} as const; + function MemorySearchSubsection({ icon, + step, title, description, children, + tone = "violet", className = "", }: { icon: ReactNode; + step: string; title: string; description: string; children: ReactNode; + tone?: keyof typeof MEMORY_SEARCH_SECTION_TONES; className?: string; }) { + const toneClasses = MEMORY_SEARCH_SECTION_TONES[tone]; + return ( -
-
-
{icon}
-
-
- {title} +
+
+
+
+ {step} +
+
+
+ {icon} +
+
+ {title} +
+

+ {description} +

-

- {description} -

+
{children}
- {children} -
+ ); } +function resolveMemorySearchSectionTone( + scope: AgentSettingsScopeId, +): keyof typeof MEMORY_SEARCH_SECTION_TONES { + return scope === "universal_defaults" ? "violet" : "emerald"; +} + function MemorySearchToggleTile({ title, description, checked, disabled, onChange, + tone = "sky", }: { title: string; description: string; checked: boolean; disabled: boolean; onChange: (checked: boolean) => void; + tone?: keyof typeof MEMORY_SEARCH_SECTION_TONES; }) { + const checkTone = + tone === "violet" + ? "text-violet-600 focus:ring-violet-500" + : tone === "emerald" + ? "text-emerald-600 focus:ring-emerald-500" + : "text-sky-600 focus:ring-sky-500"; + const accentColor = + tone === "violet" ? "#7c3aed" : tone === "emerald" ? "#059669" : "#0284c7"; + return (
- {[workspaceScope, modelScope, agentDirScope, defaultRoutingScope].some( - (scope) => scope === activeScope, - ) ? ( + {hasEffectiveFieldsInActiveScope && + activeScope !== "selected_agent_override" ? ( <> -
-
-

- {t("config.agentSettings.effectiveSectionTitle")} -

-

- {t("config.agentSettings.effectiveSectionDesc")} -

+ +
+ {workspaceScope === activeScope ? ( + +
+ + {canEdit ? ( + setWorkspaceDraft(event.target.value)} + placeholder={t("config.agentSettings.workspacePlaceholder")} + className="min-w-0 flex-1 bg-transparent text-sm text-slate-700 outline-none placeholder:text-slate-400 dark:text-slate-100 dark:placeholder:text-slate-500" + /> + ) : ( + + {isLoadingSettings + ? t("config.agentSettings.loading") + : workspaceValue} + + )} +
+
+ ) : null} + + {modelScope === activeScope ? ( + +
+ + {canEdit ? ( + + ) : ( + + {isLoadingSettings + ? t("config.agentSettings.loading") + : modelValue} + + )} +
+ {canEdit && readyModelOptions.length === 0 ? ( +
+ {t("config.agentSettings.modelNoReadyOptions")} +
+ ) : null} +
+ ) : null} + + {agentDirScope === activeScope ? ( + +
+ + {canEdit ? ( + setAgentDirDraft(event.target.value)} + placeholder={t("config.agentSettings.agentDirPlaceholder")} + className="min-w-0 flex-1 bg-transparent text-sm text-slate-700 outline-none placeholder:text-slate-400 dark:text-slate-100 dark:placeholder:text-slate-500" + /> + ) : ( + + {isLoadingSettings + ? t("config.agentSettings.loading") + : agentDirValue} + + )} +
+
+ ) : null}
+
+ + ) : null} -
- -
- - {canEdit ? ( - setWorkspaceDraft(event.target.value)} - placeholder={t("config.agentSettings.workspacePlaceholder")} - className="min-w-0 flex-1 bg-transparent text-sm text-slate-700 outline-none placeholder:text-slate-400 dark:text-slate-100 dark:placeholder:text-slate-500" - /> - ) : ( - - {isLoadingSettings - ? t("config.agentSettings.loading") - : workspaceValue} - - )} -
-
+ {defaultRoutingScope === activeScope ? ( + + ) : null} - -
- - {canEdit ? ( - - ) : ( - - {isLoadingSettings - ? t("config.agentSettings.loading") - : modelValue} - - )} -
- {canEdit && readyModelOptions.length === 0 ? ( -
- {t("config.agentSettings.modelNoReadyOptions")} + {activeScope === "selected_agent_override" ? ( +
+
+

+ {t("config.agentSettings.selectedOverrideWorkbenchTitle")} +

+

+ {t("config.agentSettings.selectedOverrideWorkbenchDesc")} +

+
+ +
+ {conditionalScopeHint} +
+ + {activeSchemaErrorEntries.length > 0 ? ( +
+
{t("config.agentSettings.schemaUnavailable")}
+
+ {activeSchemaErrorEntries.map(([path, message]) => ( +
+ {path}: {message}
- ) : null} - + ))} +
+
+ ) : null} - +
+ {agentDirScope === activeScope ? ( + } + title={t("config.agentSettings.selectedOverrideIdentityTitle")} + hint={t("config.agentSettings.selectedOverrideIdentityDesc")} metadata={agentDirMetadata} - className="lg:col-span-2" >
@@ -1398,51 +1719,178 @@ export function AgentSettingsModule() { )}
- -
-
+ + ) : null} - - + {groupChatScope === activeScope ? ( + } + title={t("config.agentSettings.selectedOverrideCollabTitle")} + hint={t("config.agentSettings.selectedOverrideCollabDesc")} + metadata={groupChatMetadata} + schema={schemas[ADVANCED_SCHEMA_PATHS.groupChat] ?? null} + loading={isLoadingSchemas} + > + + {t("config.agentSettings.groupChat")} + + {canEdit ? ( +