# OpenClaw 群晖 NAS DS925+ 运维手册

> 最后更新：2026-04-21  
> 维护者：Tyrone  
> 设备：群晖 DS925+ (OrpaonNAS)

---

## 1. 系统环境

| 项目 | 值 |
|------|-----|
| NAS 型号 | Synology DS925+ |
| NAS 主机名 | OrpaonNAS |
| NAS 局域网 IP | 192.168.0.130 |
| DSM 版本 | 7.x |
| Docker | Container Manager |
| SSH 用户 | Tyrone (UID=1026, GID=100) |

---

## 2. OpenClaw 部署信息

### 2.1 容器配置

| 项目 | 值 |
|------|-----|
| 容器名 | openclaw |
| 镜像 | `docker.1ms.run/alpine/openclaw:latest` |
| 镜像代理 | docker.1ms.run（国内加速） |
| 运行用户 | node (UID=1026, GID=100) |
| 重启策略 | always |
| 端口映射 | `0.0.0.0:18789->18789/tcp` |
| 当前版本 | v2026.4.12 |
| 最新版本 | v2026.4.14 |

### 2.2 创建容器命令

```bash
# 1) 创建专用 Docker 网络（openclaw 与 browserless 通信必须在同一网络）
docker network create openclaw-net 2>/dev/null || true

# 2) 创建 openclaw 容器（连入 openclaw-net）
docker run -d \
  --name openclaw \
  --restart always \
  --network openclaw-net \
  -p 18789:18789 \
  -v /volume1/docker/openclaw-data:/home/node/.openclaw \
  -e PUID=1026 \
  -e PGID=100 \
  docker.1ms.run/alpine/openclaw:latest
```

> ⚠️ **注意**：Volume 必须映射到 `/home/node/.openclaw`（node 用户 home 目录），**不要**映射到 `/root/.openclaw`。

### 2.3 Browserless Sidecar 容器（浏览器能力依赖）

OpenClaw 浏览器能力通过 Remote CDP 连接外置 browserless 容器实现；**必须与 openclaw 同处 `openclaw-net`**，且**不启用 TOKEN**（原因见第 12 章）。

```bash
# 创建持久化目录（首次）
sudo mkdir -p /volume1/docker/browserless-profile
sudo chown -R 1000:1000 /volume1/docker/browserless-profile
sudo chmod -R 777 /volume1/docker/browserless-profile  # 群晖必须，否则 Chrome 报 Permission denied

# 清除残留锁文件（如有）
sudo rm -f /volume1/docker/browserless-profile/SingletonLock

docker run -d \
  --name browserless \
  --restart always \
  --network openclaw-net \
  -v /volume1/docker/browserless-profile:/profile \
  -e MAX_CONCURRENT_SESSIONS=10 \
  -e KEEP_ALIVE=true \
  -e CONNECTION_TIMEOUT=600000 \
  -e DEFAULT_USER_DATA_DIR=/profile \
  -e DEFAULT_STEALTH=true \
  browserless/chrome:latest
```

参数说明：

| 环境变量 | 值 | 作用 |
|---------|-----|------|
| `MAX_CONCURRENT_SESSIONS` | 10 | 避免 OpenClaw 重连时被排队 |
| `KEEP_ALIVE` | true | 空闲时不立即销毁，稳定连续请求 |
| `CONNECTION_TIMEOUT` | 600000 | 10 分钟空闲超时 |
| `DEFAULT_USER_DATA_DIR` | /profile | Chrome 用户数据持久化（Cookie/登录态跨重启保留） |
| `DEFAULT_STEALTH` | true | 反检测模式（降低被网站识别为自动化的概率） |

> ⚠️ **不使用 `PREBOOT_CHROME`/`PREBOOT_QUANTITY`**：预热会启动多个 Chrome 实例，与 `DEFAULT_USER_DATA_DIR` 共享同一 profile 目录时触发 `SingletonLock` 互斥锁冲突，导致 Chrome 启动失败。`KEEP_ALIVE=true` 已能保证会话间复用，无需预热。

> ⚠️ **禁止**设置 `TOKEN` 环境变量。OpenClaw 在可达性探测阶段会请求 `/json/version` 但**不带** token，启用 TOKEN 会导致 403 → 被误判为 `Remote CDP not reachable`。改由 Docker 网络隔离保障安全。

> ⚠️ **群晖权限问题**：创建容器前必须先 `sudo chmod -R 777 /volume1/docker/browserless-profile`，否则群晖挂载卷默认只读权限导致 Chrome 报 `Permission denied` 无法启动。容器重启后若报 `SingletonLock: File exists`，执行 `sudo rm -rf /volume1/docker/browserless-profile/SingletonLock /volume1/docker/browserless-profile/SingletonCookie /volume1/docker/browserless-profile/SingletonSocket` 清除残留锁文件。

---

## 3. 路径映射

### 3.1 持久化数据

| NAS 宿主机路径 | 容器内路径 | 用途 |
|---------------|-----------|------|
| `/volume1/docker/openclaw-data/` | `/home/node/.openclaw/` | 配置、数据、workspace |

### 3.2 已废弃路径

| NAS 宿主机路径 | 说明 |
|---------------|------|
| `/volume1/docker/openclaw/` | 旧映射（`/root/.openclaw`），已废弃，可删除 |

### 3.3 应用程序文件（随镜像更新，不持久化）

| 容器内路径 | 用途 |
|-----------|------|
| `/app/dist/` | 编译后的 OpenClaw 核心程序 |
| `/app/skills/` | 内置技能模板 |
| `/app/extensions/` | 内置扩展 |
| `/app/openclaw.mjs` | 主入口文件 |

---

## 4. 关键文件说明

### 4.1 核心配置

| 文件 | 容器内路径 | 作用 |
|------|-----------|------|
| 主配置 | `/home/node/.openclaw/openclaw.json` | Gateway 端口/绑定/auth、模型、插件、频道、工具策略 |
| 配置备份 | `/home/node/.openclaw/openclaw.json.bak*` | 自动备份，修改出错可回滚 |
| 运行日志 | `/tmp/openclaw/openclaw-YYYY-MM-DD.log` | 容器内运行时日志，重启丢失 |

### 4.2 Agent 工作区

| 文件 | 容器内路径 | 作用 |
|------|-----------|------|
| IDENTITY.md | `workspace/IDENTITY.md` | Agent 身份：名字、角色、性格 |
| SOUL.md | `workspace/SOUL.md` | Agent 行为准则：价值观、决策逻辑 |
| AGENTS.md | `workspace/AGENTS.md` | 多 Agent 定义及能力描述 |
| TOOLS.md | `workspace/TOOLS.md` | Agent 可调用工具授权 |
| BOOTSTRAP.md | `workspace/BOOTSTRAP.md` | Agent 启动引导流程 |
| HEARTBEAT.md | `workspace/HEARTBEAT.md` | 定时心跳任务定义 |
| USER.md | `workspace/USER.md` | 用户画像与偏好 |
| state/ | `workspace/state/` | 会话状态持久化 |
| .git/ | `workspace/.git/` | 版本控制，支持回滚 |

### 4.3 插件与频道

| 路径 | 作用 |
|------|------|
| `extensions/` | 已安装插件（openclaw-weixin 等） |
| `agents/` | Agent 认证配置、auth-profiles |
| `devices/` | 已配对设备信息 |
| `qqbot/` | QQ 机器人频道配置 |
| `tasks/` | 定时任务执行记录 |
| `cron/jobs.json` | Cron 任务定义 |
| `canvas/` | Control UI 前端静态资源 |
| `identity/` | Gateway 自身身份凭证 |
| `logs/` | Gateway 运行日志 |

---

## 5. 当前配置详情

### 5.1 Gateway 配置

```json
{
  "gateway": {
    "auth": {
      "mode": "token",
      "token": "84b9255b705682edf22d0804d004f8060349067513dec98a"
    },
    "controlUi": {
      "allowInsecureAuth": true,
      "dangerouslyDisableDeviceAuth": true
    },
    "mode": "local",
    "port": 18789,
    "bind": "lan"
  }
}
```

### 5.2 模型配置

| 项目 | 值 |
|------|-----|
| 主模型 | minimax-portal/MiniMax-M2.7 |
| API 端点 | `https://api.minimaxi.com/anthropic` |
| API 协议 | anthropic-messages (Anthropic 兼容) |
| 上下文窗口 | 204,800 tokens |
| 最大输出 | 131,072 tokens |
| 推理能力 | 支持（reasoning: true） |
| 认证方式 | MiniMax OAuth CN（minimax-portal） |
| 订阅计划 | Plus（含图像理解） |

### 5.3 图像理解

| 项目 | 值 |
|------|-----|
| 状态 | ✅ 已启用 |
| 实现方式 | minimax-portal 插件内置 `registerMediaUnderstandingProvider` |
| 视觉模型 | MiniMax-VL-01（插件内部路由） |
| 启用方式 | 通过 `openclaw onboard --auth-choice minimax-cn-oauth` 完成 OAuth 认证后自动生效 |
| 注意 | 日志中 `[media-understanding] image: failed (0/1) reason=Unknown model` 为已知噪音，不影响实际图像识别功能 |

> 💡 **关键说明**：MiniMax 图像理解仅在 `minimax-portal`（OAuth）认证方式下自动生效。使用 API Key（`minimax` provider）方式时，VL-01 不会被自动注册，图像理解不可用。如需启用，必须通过 OAuth 重新认证：
> ```bash
> docker exec -it openclaw openclaw onboard --auth-choice minimax-cn-oauth
> ```

### 5.4 插件状态

| 插件 | 版本 | 状态 |
|------|------|------|
| minimax | - | ✅ 已启用 |
| openclaw-weixin | 2.1.8 | ✅ 已启用 |

### 5.5 工具与策略

| 项目 | 值 |
|------|-----|
| tools.profile | coding |
| agents.defaults.compaction.mode | safeguard |
| session.dmScope | per-channel-peer |

### 5.6 浏览器配置 (browser)

| 配置项 | 值 | 说明 |
|--------|-----|------|
| `browser.enabled` | true | 启用浏览器能力 |
| `browser.defaultProfile` | browserless | 默认 profile |
| `browser.profiles.browserless.cdpUrl` | `ws://browserless:3000` | 连向 browserless sidecar（用容器名，避免 IP 漂移） |
| `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` | true | 允许 CDP 连接私网主机 |
| `browser.ssrfPolicy.hostnameAllowlist` | `["*"]` | 通配符放行所有域名（内网环境，无需逐个添加） |
| `browser.ssrfPolicy.allowedHostnames` | `["*"]` | 与 `hostnameAllowlist` 同步 |
| `browser.remoteCdpTimeoutMs` | 30000 | CDP 整体超时（默认 15000 偏短） |
| `browser.remoteCdpHandshakeTimeoutMs` | 45000 | CDP 握手超时 |

参考片段（`openclaw.json`）：

```json
{
  "browser": {
    "enabled": true,
    "defaultProfile": "browserless",
    "remoteCdpTimeoutMs": 30000,
    "remoteCdpHandshakeTimeoutMs": 45000,
    "profiles": {
      "browserless": { "cdpUrl": "ws://browserless:3000", "color": "#00AA00" }
    },
    "ssrfPolicy": {
      "dangerouslyAllowPrivateNetwork": true,
      "hostnameAllowlist": ["*"],
      "allowedHostnames": ["*"]
    }
  }
}
```

> 使用 `["*"]` 通配符放行所有域名，无需逐个添加。如需收紧策略，改回逐域名白名单（修改时必须带齐所有值，否则会把 `browserless` 等关键主机顶掉）。

---

## 6. 访问方式

| 方式 | 地址 | 说明 |
|------|------|------|
| Control UI | `http://192.168.0.130:18789/` | 浏览器访问，需输入 Gateway Token |
| 健康检查 | `http://192.168.0.130:18789/healthz` | 返回 `{"ok":true,"status":"live"}` |
| 就绪检查 | `http://192.168.0.130:18789/readyz` | 返回就绪状态 |
| SSH 隧道 | `ssh -L 18789:127.0.0.1:18789 Tyrone@192.168.0.130` | 通过 localhost 安全访问 |

---

## 7. 常用运维命令

### 7.1 状态检查

```bash
# 容器运行状态
docker ps --filter name=openclaw

# 健康检查
curl -fsS http://192.168.0.130:18789/healthz

# 就绪检查
curl -fsS http://192.168.0.130:18789/readyz

# 资源占用
docker stats openclaw --no-stream

# 查看日志
docker logs --tail 50 openclaw
docker logs -f openclaw

# 容器详情
docker inspect openclaw --format='状态: {{.State.Status}} | 启动: {{.State.StartedAt}} | 健康: {{.State.Health.Status}}'
```

### 7.2 配置修改

> ⚠️ **变更前必备份**：每次修改 `openclaw.json`、`.env` 等关键配置前，必须先备份当前版本，以便出错时回滚。
>
> ```bash
> # 备份配置（带日期标记）
> sudo cp /volume1/docker/openclaw-data/openclaw.json /volume1/docker/openclaw-data/openclaw.json.bak-$(date +%Y%m%d)
>
> # 回滚到备份版本
> sudo cp /volume1/docker/openclaw-data/openclaw.json.bak-YYYYMMDD /volume1/docker/openclaw-data/openclaw.json
> docker restart openclaw
> ```

```bash
# 修改单个配置
docker exec openclaw node dist/index.js config set --batch-json '[{"path":"<配置路径>","value":"<值>"}]'

# 查看当前配置
docker exec -u root openclaw cat /home/node/.openclaw/openclaw.json

# 宿主机直接编辑配置（需 sudo）
sudo vi /volume1/docker/openclaw-data/openclaw.json
```

### 7.3 容器管理

```bash
# 重启
docker restart openclaw

# 停止
docker stop openclaw

# 启动
docker start openclaw

# 进入容器（普通用户）
docker exec -it openclaw sh

# 进入容器（root 用户）
docker exec -it -u root openclaw sh
```

### 7.4 文件权限修复

```bash
# docker cp 后文件属主变为宿主机用户，需修复
docker exec -u root openclaw chown -R node:node /home/node/.openclaw
```

---

## 8. 升级流程

```bash
# 1. 备份配置
docker cp openclaw:/home/node/.openclaw /volume1/docker/openclaw-backup-$(date +%Y%m%d)

# 2. 拉取最新镜像
docker pull docker.1ms.run/alpine/openclaw:latest

# 3. 停止并删除旧容器
docker stop openclaw && docker rm openclaw

# 4. 用新镜像重建
docker run -d \
  --name openclaw \
  --restart always \
  -p 18789:18789 \
  -v /volume1/docker/openclaw-data:/home/node/.openclaw \
  -e PUID=1026 \
  -e PGID=100 \
  docker.1ms.run/alpine/openclaw:latest

# 5. 修复文件权限
docker exec -u root openclaw chown -R node:node /home/node/.openclaw

# 6. 验证
sleep 15
docker logs --tail 10 openclaw
curl -fsS http://192.168.0.130:18789/healthz
```

---

## 9. 微信频道配置

```bash
# 扫码登录（交互式，需终端执行）
docker exec -it openclaw node dist/index.js channels login --channel openclaw-weixin

# 重启生效
docker restart openclaw

# 验证频道状态
docker exec openclaw node dist/index.js channels status
```

---

## 10. 已知问题与注意事项

### 10.1 安全配置

- ⚠️ `allowInsecureAuth=true`：允许 HTTP 非安全上下文认证
- ⚠️ `dangerouslyDisableDeviceAuth=true`：禁用设备身份验证，任何能访问 18789 端口的人可通过 token 控制
- 建议：配置 HTTPS 反向代理后，将以上两项恢复为 `false`

### 10.2 权限陷阱

- `/root/` 目录默认权限 `drwx------`，node 用户无法穿越
- Volume 必须映射到 `/home/node/.openclaw`，**不要**映射到 `/root/.openclaw`
- `docker cp` 备份后文件属主变为宿主机用户，需 `chown -R node:node` 修复

### 10.3 网络配置

- `gateway.bind` 必须设为 `lan`（NAS 部署场景），否则局域网无法访问
- `gateway.auth.mode` 必须为 `token`，**不要**设为 `trusted-proxy`（除非配置了 trustedProxy）

### 10.4 待完成事项

- [ ] 升级至 v2026.4.14
- [ ] 配置微信频道扫码登录
- [ ] 把 `browser` 工具曝露给 Agent（`tools.profile=coding` 不含 browser，需定位正确键名）
- [ ] 按业务需求补齐 `browser.ssrfPolicy.hostnameAllowlist`
- [ ] 轮换 `gateway.auth.token`（历史排障期间 token 曾外泄至日志）
- [ ] 配置 HTTPS 反向代理（长期方案）
- [ ] 关闭 `dangerouslyDisableDeviceAuth`（HTTPS 配置完成后）
- [ ] 运行 `openclaw security audit` 安全审计

---

## 11. 故障排查

| 症状 | 可能原因 | 解决方案 |
|------|---------|---------|
| 容器退出 (exit 137) | 内存不足 OOM | 确保 ≥4GB 可用 RAM |
| 健康检查失败 | 端口映射或 bind 配置错误 | 检查 `-p 18789:18789` 和 `gateway.bind=lan` |
| Gateway 启动失败循环 | auth 模式配置错误 | `sudo sed` 修复 openclaw.json 中的 auth.mode |
| Control UI "缺少网关令牌" | 正常认证流程 | 输入 openclaw.json 中的 gateway.auth.token |
| Control UI "需要设备身份" | HTTP 非安全上下文 | 设置 `dangerouslyDisableDeviceAuth=true` 或使用 SSH 隧道 |
| 配置修改 Permission denied | node 用户无写权限 | `docker exec -u root openclaw chown -R node:node /home/node/.openclaw` |
| pricing bootstrap failed | 网络问题 | 检查容器网络连通性，不影响核心功能 |
| `Remote CDP not reachable at ws://browserless:3000` (ms 级同步失败) | `browserless` 主机名未加入 SSRF allowlist | 把 `browserless` 与 `172.18.0.3` 加入 `browser.ssrfPolicy.hostnameAllowlist` / `allowedHostnames`（两键同步） |
| `Remote CDP not reachable` (约 15 秒超时失败) | browserless 并发不足或超时过短 | 用 12.2 的参数重建 browserless，并把 CDP 超时放宽（见 5.5） |
| `Remote CDP not reachable` 且 `/json/version` 返回 403 | browserless 启用了 `TOKEN` | 去掉 `TOKEN` 环境变量，改由 Docker 网络隔离 |
| `browser` CLI 子命令不存在 | `plugins.allow` 未包含 `browser` | 把 `browser` 加入 `plugins.allow` 并重启 |
| `browser navigation blocked by policy` | 目标域名未在 SSRF allowlist | 将目标域名加入 `browser.ssrfPolicy.hostnameAllowlist` |
| `browser status` 显示 `running:false` 但 `tabs` 正常 | Remote CDP 模式下 `status` 只读托管状态 | 忽略 `status`，以 `tabs`/`open` 为准（非 bug） |
| Agent 在 Control UI 里自称"没有 browser 工具" | `tools.profile=coding` 预设不含 browser | 待定位正确键名（见 10.4 待办） |
| `Model does not support images: minimax/MiniMax-M2.7` | 使用 API Key 认证（`minimax` provider），VL-01 未注册 | 切换到 OAuth 认证（见 §14） |
| `[media-understanding] image: failed (0/1) reason=Unknown model` | Gateway 模型查找路径找不到 VL-01 | 已知日志噪音，不影响实际图像识别（见 §14.4） |
| `[bundle-mcp] failed to start server "minimax-vision"` | MCP server 超时（uvx 首次下载包） | 不需要 MCP server，OAuth 方式自动处理（见 §14.3） |
| `config reload skipped: Unrecognized key: "mediaModel"` | `agents.defaults.mediaModel` 不是合法配置项 | 用 `openclaw doctor --fix` 清除，或手动删除 |
| `config reload skipped: Unrecognized key: "imageModel"` | `agents.defaults.imageModel` 指向不存在的模型 | 删除该配置项，让插件自动路由（见 §14.4） |

---

## 12. 浏览器能力（Remote CDP + Browserless）排障与调优

### 12.1 工作原理与关键事实

- OpenClaw 自身**不内置 Chrome**，浏览器能力通过 Remote CDP 连接外部 `browserless` 容器实现。
- `browser` 插件需在 `plugins.allow` 列出并启用（`browser.enabled=true`），重启后 `browser` 才会出现在 CLI 顶层命令。
- OpenClaw 会**在发起连接前同步校验 `cdpUrl` 的主机**：
  - **IP 形式**（如 `172.18.0.3`）走 `dangerouslyAllowPrivateNetwork` 私网放行
  - **主机名形式**（如 `browserless`）**必须**进入 `hostnameAllowlist` / `allowedHostnames`
  - 未通过校验时错误统一为 `Remote CDP ... not reachable`，失败耗时在 **毫秒级**（与网络超时区分关键特征）
- OpenClaw 对 browserless 的可达性探测会请求 `/json/version`**但不附带 token**，因此 browserless **必须**关闭 TOKEN。
- `browser status` 在 Remote CDP 模式下**始终显示 `running:false`**（它读的是"托管会话"状态），以 `tabs`/`open` 为准。

### 12.2 一次到位的部署配置（生产建议值）

Browserless：
```bash
sudo mkdir -p /volume1/docker/browserless-profile
sudo chmod -R 777 /volume1/docker/browserless-profile

docker run -d --name browserless --restart always \
  --network openclaw-net \
  -v /volume1/docker/browserless-profile:/profile \
  -e MAX_CONCURRENT_SESSIONS=10 \
  -e KEEP_ALIVE=true \
  -e CONNECTION_TIMEOUT=600000 \
  -e DEFAULT_USER_DATA_DIR=/profile \
  -e DEFAULT_STEALTH=true \
  browserless/chrome:latest
```

OpenClaw 端（`plugins.allow` 含 `browser`，且已应用 5.5 的 `browser` 配置）：
```bash
docker exec openclaw node dist/index.js config set --batch-json '[
  {"path":"plugins.allow","value":["openclaw-weixin","minimax","memory-core","browser"]},
  {"path":"browser.profiles.browserless.cdpUrl","value":"ws://browserless:3000"},
  {"path":"browser.remoteCdpTimeoutMs","value":30000},
  {"path":"browser.remoteCdpHandshakeTimeoutMs","value":45000},
  {"path":"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork","value":true},
  {"path":"browser.ssrfPolicy.hostnameAllowlist","value":["*"]},
  {"path":"browser.ssrfPolicy.allowedHostnames","value":["*"]}
]'
docker restart openclaw
```

### 12.3 三层验证

**第 1 层：网络/CDP 协议可用性（openclaw 容器内执行）**
```bash
# HTTP 无 token 必须 200
docker exec openclaw node -e "
(async () => {
  const r = await fetch('http://browserless:3000/json/version');
  console.log('status=', r.status);
})();"

# WebSocket CDP 握手必须有 Browser.getVersion 回包
docker exec openclaw node -e "
const WebSocket=require('ws');
const ws=new WebSocket('ws://browserless:3000');
ws.on('open',()=>ws.send(JSON.stringify({id:1,method:'Browser.getVersion'})));
ws.on('message',(d)=>{console.log('REPLY',d.toString()); process.exit(0);});
ws.on('error',(e)=>{console.error('ERR',e.message); process.exit(1);});
setTimeout(()=>{console.error('TIMEOUT');process.exit(2);},15000);"
```

**第 2 层：OpenClaw CLI 闭环**
```bash
docker exec openclaw node dist/index.js browser --browser-profile browserless start
docker exec openclaw node dist/index.js browser --browser-profile browserless open https://example.com
docker exec openclaw node dist/index.js browser --browser-profile browserless tabs
docker exec openclaw node dist/index.js browser --browser-profile browserless snapshot
```

**第 3 层：稳定性（连续独立调用）**
```bash
for i in 1 2 3; do
  echo "---- round $i ----"
  docker exec openclaw node dist/index.js browser --browser-profile browserless open https://example.com
done
```

三轮全部返回 `opened: https://example.com/` 才算稳定闭环。

### 12.4 历史定位过程（本次排障复盘）

| # | 现象 | 根因 | 修复 |
|---|------|------|------|
| 1 | `Remote CDP not reachable`（ms 级） | `cdpUrl` 用容器名 `browserless`，但未入 `hostnameAllowlist` | 将 `browserless` 加入 allowlist（两个键同步） |
| 2 | `Remote CDP not reachable`（/json/version 403） | browserless 启用了 `TOKEN`，OpenClaw 探测不带 token | 去掉 `TOKEN` 环境变量，改由网络隔离 |
| 3 | 独立 CLI 调用 `open` 间歇性 15s 超时失败 | browserless 并发不足、OpenClaw 超时偏短 | 扩容 `MAX_CONCURRENT_SESSIONS=10` + `KEEP_ALIVE` + `remoteCdpTimeoutMs=30000` |
| 4 | `status` 显示 `running:false` 但 `tabs` 正常 | Remote CDP 模式 `status` 只读托管状态 | 非 bug，忽略 `status` |
| 5 | Agent 在 UI 里说"没有 browser 工具" | `tools.profile=coding` 预设不含 browser（待定位 key） | 见 10.4 待办 |
| 6 | Chrome 报 `SingletonLock: File exists` 或 `Permission denied` | `PREBOOT_CHROME` 多实例争抢同一 profile 目录，或群晖权限过严 | 去掉 `PREBOOT_CHROME`/`PREBOOT_QUANTITY`，`sudo chmod -R 777` 目录，清除残留锁文件 |

### 12.5 Browserless 容器常用运维

```bash
# 查看 IP（通常稳定为 openclaw-net 内第 3 个 IP）
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' browserless

# 查看是否启用 TOKEN（必须 NO_TOKEN_ENV）
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' browserless | grep -E '^TOKEN=' || echo "NO_TOKEN_ENV"

# 查看 browserless 日志
docker logs --tail 100 browserless

# 重建 browserless（保持运维建议参数，见 12.2）
docker stop browserless && docker rm browserless
# 再执行 12.2 的 run 命令
```

### 12.6 Browserless 重装速查清单

> 多次排障经验汇总，重装时按此清单逐项检查，避免重复踩坑。

#### 步骤 1：创建持久化目录（必须在 `docker run` 之前）

```bash
sudo mkdir -p /volume1/docker/browserless-profile
sudo chown -R 1000:1000 /volume1/docker/browserless-profile
sudo chmod -R 777 /volume1/docker/browserless-profile  # 群晖必须！否则 Chrome 报 Permission denied
```

> ⚠️ **如果目录不存在就执行 `docker run -v ...`，Docker 会静默忽略挂载参数，`Binds` 为 null，profile 不持久化。**

验证：`ls -ld /volume1/docker/browserless-profile/` 必须返回目录信息。

#### 步骤 2：清除残留锁文件（重启/重装前）

```bash
sudo rm -rf /volume1/docker/browserless-profile/SingletonLock \
            /volume1/docker/browserless-profile/SingletonCookie \
            /volume1/docker/browserless-profile/SingletonSocket
```

> ⚠️ **不清理会导致 Chrome 报 `SingletonLock: File exists` 启动失败。**

#### 步骤 3：创建容器

```bash
docker run -d --name browserless --restart always \
  --network openclaw-net \
  -v /volume1/docker/browserless-profile:/profile \
  -e MAX_CONCURRENT_SESSIONS=10 \
  -e KEEP_ALIVE=true \
  -e CONNECTION_TIMEOUT=600000 \
  -e DEFAULT_USER_DATA_DIR=/profile \
  -e DEFAULT_STEALTH=true \
  browserless/chrome:latest
```

> ⚠️ **不要使用 `PREBOOT_CHROME=true` / `PREBOOT_QUANTITY=2`**：预热会启动多个 Chrome 实例，与 `DEFAULT_USER_DATA_DIR` 共享同一 profile 目录时触发 `SingletonLock` 互斥锁冲突，导致 Chrome 启动失败报 `Issue launching Chrome, retries exhausted`。`KEEP_ALIVE=true` 已能保证会话间复用。

> ⚠️ **不要设置 `TOKEN` 环境变量**：OpenClaw 可达性探测请求 `/json/version` 不带 token，启用 TOKEN 导致 403 → 被误判为 `Remote CDP not reachable`。

#### 步骤 4：验证挂载生效

```bash
docker inspect browserless --format='{{json .HostConfig.Binds}}'
# 必须返回: ["/volume1/docker/browserless-profile:/profile"]
# 如果返回 null → 目录不存在时创建的容器，需删除重建

docker exec browserless ls -la /profile/
# 必须返回目录列表（首次为空但能看到 . 和 ..）
```

#### 步骤 5：等待 Chrome 启动

```bash
sleep 15 && docker logs --tail 10 browserless
# 必须看到 "Running on port 3000" 或 "Chrome launched"
# 如果看到 "Issue launching Chrome, retries exhausted" → 检查权限/锁文件
```

#### 步骤 6：配置 OpenClaw SSRF 策略

```bash
docker exec openclaw node dist/index.js config set --batch-json '[
  {"path":"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork","value":true},
  {"path":"browser.ssrfPolicy.hostnameAllowlist","value":["*"]},
  {"path":"browser.ssrfPolicy.allowedHostnames","value":["*"]}
]'
docker restart openclaw
```

> ⚠️ **SSRF allowlist 会在 OpenClaw 重启时被清空为 `[]`**（已知行为）。如果 `open` 报 `not reachable` 且耗时 ~140ms（毫秒级），就是 SSRF 拦截，不是真正的网络问题。重新执行上述命令即可。

#### 步骤 7：端到端验证

```bash
# 第 1 层：HTTP 可达
docker exec openclaw node -e "
(async () => {
  const r = await fetch('http://browserless:3000/json/version');
  console.log('status=', r.status);
})();"
# 期望: status= 200

# 第 2 层：WebSocket CDP 握手
docker exec openclaw node -e "
const WebSocket=require('ws');
const ws=new WebSocket('ws://browserless:3000');
ws.on('open',()=>ws.send(JSON.stringify({id:1,method:'Browser.getVersion'})));
ws.on('message',(d)=>{console.log('REPLY',d.toString()); process.exit(0);});
ws.on('error',(e)=>{console.error('ERR',e.message); process.exit(1);});
setTimeout(()=>{console.error('TIMEOUT');process.exit(2);},15000);"
# 期望: REPLY {"id":1,"result":{...}}

# 第 3 层：OpenClaw CLI 闭环
docker exec openclaw node dist/index.js browser --browser-profile browserless open https://www.baidu.com
# 期望: opened: https://www.baidu.com/
```

#### 故障速判表

| 现象 | 耗时 | 根因 | 修复 |
|------|------|------|------|
| `ECONNREFUSED` | - | browserless 未启动或端口不通 | `docker logs browserless` 检查启动状态 |
| `Permission denied` 创建 SingletonLock | - | `/profile/` 权限不足 | `sudo chmod -R 777 /volume1/docker/browserless-profile` |
| `SingletonLock: File exists` | - | 残留锁文件 | `sudo rm -rf .../SingletonLock .../SingletonCookie .../SingletonSocket` |
| `Issue launching Chrome, retries exhausted` | - | PREBOOT 多实例锁冲突 | 去掉 `PREBOOT_CHROME`/`PREBOOT_QUANTITY` |
| `Binds: null` | - | 目录不存在时创建容器 | 先 `mkdir` + `chmod`，再 `docker run` |
| `not reachable` + ~140ms | 毫秒级 | SSRF 策略拦截 | 重新设置 `hostnameAllowlist: ["*"]` |
| `not reachable` + 403 | - | browserless 启用了 TOKEN | 去掉 `TOKEN` 环境变量 |
| `not reachable` + 15s+ | 秒级 | 真正的网络/超时问题 | 检查容器网络、CDP 超时配置 |

---

## 13. Agent 人设与 Workspace 维护

本章记录 OpenClaw Agent（代号 **小橙**）的长期记忆结构与维护流程。源文件在 `w:\ProRepo\NASOpenClaw\workspace\`（本地 Git 管理），部署到容器内 `/home/node/.openclaw/workspace/`（宿主机 `/volume1/docker/openclaw-data/workspace/`）。

### 13.1 Workspace 目录语义

| 路径 | 作用 | 维护者 |
|------|------|-------|
| `README.md` | Workspace 总说明 | Tyrone |
| `USER.md` | 主人画像、公司档案、合规红线、渠道矩阵、KPI | Tyrone |
| `IDENTITY.md` | 小橙的身份、职责、边界 | Tyrone |
| `SOUL.md` | 小橙的决策准则与行为规约（最重要的运行时提示） | Tyrone |
| `AGENTS.md` | 多 Agent / 多技能分工 | Tyrone |
| `TOOLS.md` | 工具授权与使用准则 | Tyrone |
| `BOOTSTRAP.md` | 启动引导与自检 | Tyrone |
| `HEARTBEAT.md` | 定时心跳任务定义 | Tyrone |
| `insights.md` | 经验沉淀（小橙随反馈持续追加） | 小橙 |
| `brand/voice-style.md` | 文笔风格规约（产业观察笔法） | Tyrone |
| `brand/banned-words.md` | 广告法 / 行业合规禁用词库 | Tyrone |
| `products/README.md` | 产品与解决方案线索引 | Tyrone |
| `products/_template.md` | 产品页模板 | — |
| `products/*.md` | 各方案详情页（数据源） | Tyrone |
| `templates/*.md` | 21 个平台发文模板 | Tyrone |
| `assets/` | 媒体素材（图片 / 视频 / PPT） | Tyrone |
| `drafts/` | 待复核草稿（小橙产出） | 小橙 |
| `published/` | 已发布归档 + 链接 + 数据快照 | 小橙 |
| `reports/` | 日报 / 周报 / 复盘 | 小橙 |
| `state/` | 会话状态、API 调用日志 | OpenClaw 自动 |

### 13.2 推送到 NAS 容器（首次部署）

本地 `workspace/` 编辑完成后，推送到 NAS：

```bash
# 方式 A：scp 整个目录（推荐首次使用）
scp -r w:/ProRepo/NASOpenClaw/workspace Tyrone@192.168.0.130:/tmp/workspace-new

# SSH 到 NAS 后
ssh Tyrone@192.168.0.130
sudo mv /volume1/docker/openclaw-data/workspace /volume1/docker/openclaw-data/workspace.bak-$(date +%Y%m%d) 2>/dev/null
sudo mv /tmp/workspace-new /volume1/docker/openclaw-data/workspace

# 修复权限（容器内 node:node）
docker exec -u root openclaw chown -R node:node /home/node/.openclaw/workspace

# 初始化 workspace 下的 git（便于版本管理）
docker exec -u node openclaw sh -c "cd /home/node/.openclaw/workspace && git init && git add -A && git commit -m 'init: workspace scaffolding'"

# 重启生效
docker restart openclaw
```

### 13.3 推送到 NAS 容器（日常增量更新）

改动某几个 md 文件后：

```bash
# 本地改完先 git commit 记录
cd w:/ProRepo/NASOpenClaw
git add workspace/
git commit -m "update: <what changed>"

# 用 rsync 增量同步（省带宽、保留容器侧 drafts/published/）
rsync -av --delete \
  --exclude 'drafts/' --exclude 'published/' --exclude 'reports/' \
  --exclude 'state/' --exclude '.git/' --exclude 'assets/' \
  w:/ProRepo/NASOpenClaw/workspace/ \
  Tyrone@192.168.0.130:/volume1/docker/openclaw-data/workspace/

# 修复权限
ssh Tyrone@192.168.0.130 "docker exec -u root openclaw chown -R node:node /home/node/.openclaw/workspace"

# 通知小橙重读（不用重启容器，小橙下一轮对话自动读取）
```

> **注意**：`drafts/` `published/` `reports/` `state/` 由小橙在容器内写入，**本地不保留**，rsync 必须加 `--exclude`，否则会被回覆盖。

### 13.4 日常维护节奏

| 频率 | 动作 | 负责 |
|------|------|------|
| 每次调教后 | 本地改 USER/IDENTITY/SOUL → rsync → 生效 | Tyrone |
| 每周一次 | 审阅 `insights.md`，把重要经验固化进 `SOUL.md` 或 `voice-style.md` | Tyrone |
| 每月一次 | 审阅 `products/` 是否补齐新案例 | Tyrone |
| 每季度一次 | 审阅 `templates/` 平台政策是否变化（发布流程、话题标签、合规规则） | Tyrone |
| 随时 | Git 提交 + 推送到 GitHub/Gitea 做异地备份 | Tyrone |

### 13.5 首次启动后必做的事

1. **在 `products/` 下至少建 3 个方案页**（建议 `mes.md` / `scada.md` / `machine-vision.md`）。
2. 在 Control UI 跟小橙说："**自检**"，确认 `BOOTSTRAP.md` 的 4 项自检全过。
3. 给各社交/电商平台挨个扫码登录（由 Tyrone 手动完成），登录态存在 `/volume1/docker/browserless-profile/`（需先按本文 §13.6 重建 browserless 启用持久化 profile）。
4. 用一轮真实选题测试小橙的产出质量，依据反馈修订 `voice-style.md` 与 `insights.md`。

### 13.6 Browserless 重建（启用持久化登录态 + Stealth）

为让四大平台（小红书 / 抖音 / 淘宝 / LinkedIn 等）登录态长期保留，需重建 browserless：

```bash
# 停止旧容器
docker stop browserless && docker rm browserless

# 创建持久化 profile 目录（NAS 宿主机）
sudo mkdir -p /volume1/docker/browserless-profile
sudo chown -R 1000:1000 /volume1/docker/browserless-profile
sudo chmod -R 777 /volume1/docker/browserless-profile  # 群晖必须，否则 Chrome 报 Permission denied

# 清除残留锁文件（如有）
sudo rm -f /volume1/docker/browserless-profile/SingletonLock

# 重建（带 stealth + 持久化 profile，不预热避免 SingletonLock 冲突）
docker run -d --name browserless --restart always \
  --network openclaw-net \
  -v /volume1/docker/browserless-profile:/profile \
  -e MAX_CONCURRENT_SESSIONS=10 \
  -e KEEP_ALIVE=true \
  -e CONNECTION_TIMEOUT=600000 \
  -e DEFAULT_USER_DATA_DIR=/profile \
  -e DEFAULT_STEALTH=true \
  browserless/chrome:latest
```

### 13.7 配置 SSRF Allowlist

内网环境使用通配符放行所有域名，无需逐个添加：

```bash
docker exec openclaw node dist/index.js config set --batch-json '[
  {"path":"browser.ssrfPolicy.dangerouslyAllowPrivateNetwork","value":true},
  {"path":"browser.ssrfPolicy.hostnameAllowlist","value":["*"]},
  {"path":"browser.ssrfPolicy.allowedHostnames","value":["*"]}
]'
docker restart openclaw
```

> 如需收紧策略，改回逐域名白名单。注意：`hostnameAllowlist` / `allowedHostnames` 是**整体覆盖**类配置，修改时必须带齐所有值（含 `browserless`），否则会把关键主机顶掉导致 CDP 连接失败。

### 13.8 把 `browser` 工具曝露给默认 Agent

当前 `tools.profile=coding` 不含 browser（见 §10.4 待办）。可切换到通用 profile：

```bash
# 方案 A（整体切换 profile，推荐先试）
docker exec openclaw node dist/index.js config set --batch-json '[
  {"path":"tools.profile","value":"general"}
]'
docker restart openclaw

# 在 Control UI 里让小橙执行 "browser tabs" 验证是否可见
# 若 general 也不含，改用方案 B：在 Agent 配置里单独加 allowedTools（通过 UI 或 config 具体 key 待定位）
```

### 13.9 小橙对话 / 指令范式速查

下列为 Tyrone 在 Control UI 与小橙交互时的高频指令模板。小橙在 `SOUL.md` 与 `HEARTBEAT.md` 中对应了这些关键词的行为。

#### 13.9.1 日常启动 / 状态

| 指令 | 触发行为 |
|------|---------|
| `自检` | 小橙执行 `BOOTSTRAP.md` 四项自检并回报 |
| `今天干什么` | 拉待复核草稿 + 昨日数据 + 今日待办（等同 Heart beat 开场白） |
| `小橙休假` | 挂起所有心跳任务，仅保留 inbox-sweep |
| `小橙上班` | 恢复全部心跳 |

#### 13.9.2 选题与内容生产

| 指令 | 触发行为 |
|------|---------|
| `给我 3 个选题` / `本周选题` | 产出 3 候选，等 Tyrone 回"选 N" |
| `追热点 <关键词>` | 立即抓取该方向热点，产出 3 候选 |
| `写 <选题>` | 进入母版生成流程（先出 3 个标题 + 大纲） |
| `这版不行，<原因>` | 重写并把反馈沉淀到 `insights.md` |
| `改成 <平台>` | 按目标平台模板改写母版 |
| `一稿多发 <主题>` | 按 `USER.md` §5 矩阵全覆盖改写 |

#### 13.9.3 发布与复核

| 指令 | 触发行为 |
|------|---------|
| `待复核` | 列出 `drafts/` 所有未发稿，分平台展示 |
| `预览 <文件名>` | 展示完整稿件 |
| `确认发布 <文件名>` | 进入发布执行流程，停在"发布"按钮前 |
| `一键发布今日所有草稿` | 批量进入发布流程（每篇仍停在发布前等 Tyrone 点） |
| `撤 <平台> <主题>` | 从 published 找对应稿件，提示 Tyrone 手动撤回（小橙不碰删除） |
| `风险停机` | 立即停所有自动任务，进入只读模式 |

#### 13.9.4 数据与复盘

| 指令 | 触发行为 |
|------|---------|
| `数据` / `昨天数据` | 产出简版日报 |
| `本周复盘` | 产出周报（不等 cron） |
| `哪条最火` | 列近 7 天 Top3 文 / 视频 |
| `小红书最近怎么样` | 单平台 7 天纵览 |

#### 13.9.5 知识沉淀与调教

| 指令 | 触发行为 |
|------|---------|
| `记住：<规则>` | 追加到 `insights.md` + memory-core |
| `以后都这样做` | 同上，但归入"永久规则"段 |
| `忘掉 <规则>` | 从 `insights.md` 标注为已废弃（不删，只标注） |
| `读一下 products/mes.md` | 小橙重新加载指定文件作为当前上下文 |

#### 13.9.6 线索与商机

| 指令 | 触发行为 |
|------|---------|
| `今天有询盘吗` | 从 inbox-sweep 结果里筛命中 `报价/合作/定制/方案` 的对话 |
| `跟进 <客户标识>` | 生成 WhatsApp / LinkedIn 跟进话术草稿 |
| `新增客户线索：<公司> <需求>` | 写入 memory-core + `insights.md` |

#### 13.9.7 异常时的求助句式（Tyrone 可参考）

- `小橙别慌，<问题描述>` — 小橙进入"只读 + 诊断"模式，只分析不动手
- `停！` — 立即终止当前所有任务，等下一条指令
- `回答我就好，不要动手` — 本轮只对话不执行工具调用

### 13.10 待完成事项（接力自 §10.4）

- [ ] 首次推送 workspace 到 NAS（§13.2）
- [ ] 按 §13.5 补齐至少 3 个 `products/*.md`
- [ ] 按 §13.6 重建 browserless 启用持久化 profile + stealth
- [ ] 按 §13.7 批量补齐 SSRF allowlist
- [ ] 按 §13.8 把 `browser` 工具曝露给默认 Agent
- [ ] 各社交/电商平台在 browserless profile 内挨个扫码登录
- [ ] 配置 `cron/jobs.json` 对应 `HEARTBEAT.md` 的心跳任务

---

## 14. MiniMax 图像理解排障专题

> 2026-04-21 排障复盘。从"Model does not support images"到成功启用图像理解，历时约 2 小时。

### 14.1 核心结论

| 认证方式 | Provider ID | 图像理解 | 说明 |
|---------|------------|---------|------|
| API Key | `minimax` | ❌ 不可用 | VL-01 不被注册，Gateway 丢弃图片 |
| OAuth CN | `minimax-portal` | ✅ 自动生效 | 插件通过 `registerMediaUnderstandingProvider` 内部路由 VL-01 |

**唯一正确路径**：通过 `minimax-cn-oauth` 完成 OAuth 认证，切换到 `minimax-portal` provider，图像理解自动生效。

### 14.2 当前配置状态（2026-04-21）

| 项目 | 值 |
|------|-----|
| 主模型 | `minimax-portal/MiniMax-M2.7` |
| 认证方式 | MiniMax OAuth CN |
| 订阅计划 | Plus（含图像理解） |
| 图像理解 | ✅ 已启用（插件内部路由 MiniMax-VL-01） |
| `models list` 显示 | `minimax-portal/MiniMax-M2.7`（text）、`minimax-portal/MiniMax-M2.7-highspeed`（text） |
| VL-01 是否在 models list | 否（通过插件 `registerMediaUnderstandingProvider` 内部注册，不走模型目录） |

### 14.3 走过的弯路（避免重复）

| # | 尝试 | 结果 | 原因 |
|---|------|------|------|
| 1 | 在 `models.providers.minimax.models` 中添加 VL-01 | ❌ 模型不被注册 | MiniMax 插件启动时覆盖模型列表，手动添加被忽略 |
| 2 | 配置 `tools.media.image` 路由到 VL-01 | ❌ "Unknown model" | Gateway 找不到 VL-01 模型定义 |
| 3 | 配置 `plugins.entries.minimax.config.vision` | ❌ Schema 不允许 | `config reload skipped: invalid config: must NOT have additional properties` |
| 4 | 配置 `agents.defaults.mediaModel` | ❌ 不是合法配置项 | `Unrecognized key: "mediaModel"` |
| 5 | 创建独立 `minimax-vision` provider | ❌ "Unknown model" + 网络错误 | Token Plan API key 无法直接调用 VL-01 模型 API |
| 6 | 注册 `minimax-coding-plan-mcp` MCP server | ❌ 超时 30s | uvx 首次下载包耗时过长；且 OAuth 方式不需要此 MCP |
| 7 | 配置 `agents.defaults.imageModel` | ❌ "Unknown model" | 指向的模型在 models list 中不存在 |
| 8 | **`openclaw onboard --auth-choice minimax-cn-oauth`** | ✅ 成功 | OAuth 方式自动注册 `registerMediaUnderstandingProvider` |

### 14.4 已知遗留问题

**`[media-understanding] image: failed (0/1) reason=Unknown model`**

- **现象**：每次发图片时，日志中出现此警告
- **影响**：无。图片识别正常工作，minimax-portal 插件内部处理了图像理解
- **原因**：Gateway 的模型查找路径尝试在模型目录中找 image-capable 模型，但 VL-01 不在 `models list` 中（它通过插件 API 注册，不走模型目录）
- **处理**：可忽略。若要消除此日志，可在 `minimax-portal` provider 下手动添加 VL-01 模型定义（见下方脚本）

```bash
# 可选：消除 Unknown model 日志噪音
sudo tee /tmp/fix-vl01.py > /dev/null << 'PYEOF'
import json
path='/volume1/docker/openclaw-data/openclaw.json'
d=json.load(open(path))
portal = d['models']['providers']['minimax-portal']
if not portal.get('models'):
    portal['models'] = []
existing_ids = [m['id'] for m in portal['models']]
if 'MiniMax-VL-01' not in existing_ids:
    portal['models'].append({
        'id': 'MiniMax-VL-01',
        'name': 'MiniMax VL-01',
        'reasoning': False,
        'input': ['text', 'image'],
        'contextWindow': 131072,
        'maxTokens': 4096
    })
with open(path,'w') as f:
    json.dump(d,f,indent=2,ensure_ascii=False)
print('Done')
PYEOF
sudo python3 /tmp/fix-vl01.py
docker restart openclaw
```

### 14.5 启用图像理解的完整步骤（从零开始）

如果未来需要重新配置，按以下步骤操作：

```bash
# 1. 备份当前配置
sudo cp /volume1/docker/openclaw-data/openclaw.json /volume1/docker/openclaw-data/openclaw.json.bak-$(date +%Y%m%d%H%M)

# 2. 执行 OAuth 认证（会打开浏览器授权）
docker exec -it openclaw openclaw onboard --auth-choice minimax-cn-oauth
# 终端会显示一个 URL，在浏览器中打开并授权
# 选择 "Update values" 保留现有配置
# 频道选择 "Skip for now"（微信已有）

# 3. 重启
docker restart openclaw

# 4. 验证
docker logs --tail 20 openclaw | grep "agent model"
# 应显示: agent model: minimax-portal/MiniMax-M2.7

# 5. 微信发图片测试
```

### 14.6 参考文档

- [OpenClaw MiniMax Provider 文档](https://docs.openclaw.ai/providers/minimax)
- [OpenClaw Media Understanding 文档](https://docs.openclaw.ai/nodes/media-understanding)
- [MiniMax Token Plan MCP Guide](https://platform.minimax.io/docs/token-plan/mcp-guide)
- [MiniMax OpenClaw 配置指南](https://platform.minimax.io/docs/token-plan/openclaw)
- [GitHub Issue #65283](https://github.com/openclaw/openclaw/issues/65283) — MiniMax Coding Plan image tool fails

