feat: 飞书官方插件迁移 + 配对审批 + Gateway防卡死 + 微信升级修复 + 更新检测修复

- 飞书渠道从 @openclaw/feishu 迁移到 @larksuite/openclaw-lark 官方插件
- 保存飞书配置时自动禁用旧 feishu 插件,防止新旧插件冲突
- 所有主要渠道(飞书/Telegram/Discord/Slack)启用配对审批UI
- gateway_command 增加20s超时,超时后force-kill+fresh start
- 全平台启动前端口占用检查,防止Guardian无限拉起
- Linux gateway_command 补齐 Duration 导入和 cleanup_zombie 实现
- Guardian自动守护在Tauri桌面端也启用,轮询间隔30s→15s
- 微信渠道:升级操作不再弹出扫码二维码,按钮文案区分安装/升级
- 版本更新检测:CI不再将minAppVersion写死为当前版本
- 部署脚本增强OpenClaw检测,支持已安装的官方版
- 日间/夜间模式圆形扩散切换动画(View Transitions API)
- API错误信息完整展示(429限流等),URL自动转可点击链接
- 第三方API接入引导优化:移除内置密钥,引导式流程
- 修复全平台 Clippy 警告(strip_prefix/dead_code/unnecessary_unwrap等)
- Rust代码格式化修复(cargo fmt)
- toast组件支持HTML内容渲染
- Rust后端test_model返回详细错误信息
This commit is contained in:
晴天
2026-03-23 20:37:48 +08:00
parent dccb4b4dbf
commit 3687e26d5d
50 changed files with 8055 additions and 2715 deletions

View File

@@ -1772,7 +1772,7 @@ const handlers = {
install_qqbot_plugin() {
const bin = findOpenclawBin() || 'openclaw'
try {
execSync(`${bin} plugins install @sliverp/qqbot@latest`, { timeout: 60000, cwd: homedir() })
execSync(`${bin} plugins install @tencent-connect/openclaw-qqbot@latest`, { timeout: 600000, cwd: homedir() })
return '安装成功'
} catch (e) {
throw new Error('QQBot 插件安装失败: ' + (e.message || e))
@@ -3122,6 +3122,75 @@ const handlers = {
return result
},
// Agent 渠道绑定管理
list_all_bindings() {
const cfg = readConfig()
const bindings = cfg.bindings || []
return { bindings }
},
save_agent_binding({ agentId, channel, accountId, bindingConfig }) {
const cfg = readConfig()
if (!cfg.bindings) cfg.bindings = []
const bindings = cfg.bindings
// 构建新绑定
const newBinding = {
type: 'route',
agentId,
match: {
channel,
...(accountId ? { accountId } : {}),
},
}
// 合并 peer 配置到 match
if (bindingConfig && typeof bindingConfig === 'object') {
if (bindingConfig.peer) {
newBinding.match.peer = bindingConfig.peer
}
}
// 查找并更新现有绑定(相同 agentId + channel + accountId
const accountKey = accountId || ''
let found = false
for (let i = 0; i < bindings.length; i++) {
const b = bindings[i]
if (b.agentId === agentId && b.match?.channel === channel) {
const existingAccount = b.match?.accountId || ''
if (existingAccount === accountKey) {
bindings[i] = newBinding
found = true
break
}
}
}
if (!found) {
bindings.push(newBinding)
}
saveConfig(cfg)
return { ok: true }
},
delete_agent_binding({ agentId, channel, accountId }) {
const cfg = readConfig()
if (!cfg.bindings) cfg.bindings = []
const bindings = cfg.bindings
const accountKey = accountId || ''
const before = bindings.length
cfg.bindings = bindings.filter(b => {
if (b.agentId !== agentId) return true
if (b.match?.channel !== channel) return true
const existingAccount = b.match?.accountId || ''
return existingAccount !== accountKey
})
saveConfig(cfg)
return { ok: true, removed: before - cfg.bindings.length }
},
// 记忆文件
list_memory_files({ category, agent_id }) {
const suffix = agent_id && agent_id !== 'main' ? `/agents/${agent_id}` : ''

536
scripts/docker-deploy.sh Normal file
View File

@@ -0,0 +1,536 @@
#!/bin/bash
# =============================================================================
# ClawPanel Docker 部署脚本
# =============================================================================
# 功能:
# 1. 检查 Docker 环境
# 2. 构建 Docker 镜像
# 3. 启动/停止/重启容器
# 4. 查看日志
# 5. 常见问题排查
# =============================================================================
set -e
# -----------------------------------------------------------------------------
# 颜色定义
# -----------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# -----------------------------------------------------------------------------
# 配置
# -----------------------------------------------------------------------------
CONTAINER_NAME="clawpanel"
IMAGE_NAME="clawpanel"
IMAGE_TAG="latest"
DEFAULT_PORT=1420
CONFIG_DIR="$HOME/.openclaw"
DATA_DIR="$(pwd)/data"
# -----------------------------------------------------------------------------
# 工具函数
# -----------------------------------------------------------------------------
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
separator() {
echo "--------------------------------------------------------------------------------"
}
# -----------------------------------------------------------------------------
# 检查 Docker 环境
# -----------------------------------------------------------------------------
check_docker() {
log_step "检查 Docker 环境..."
# 检查 Docker 是否安装
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装或不在 PATH 中"
echo ""
echo "请先安装 Docker:"
echo " Ubuntu/Debian: curl -fsSL https://get.docker.com | sh"
echo " CentOS/RHEL: yum install -y docker-ce"
echo " Arch Linux: pacman -S docker"
exit 1
fi
# 检查 Docker 服务是否运行
if ! docker info &> /dev/null; then
log_error "Docker 服务未运行"
echo ""
echo "请启动 Docker 服务:"
echo " sudo systemctl start docker"
exit 1
fi
# 检查 Docker Compose
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
log_info "Docker Compose v2 可用"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
log_info "Docker Compose v1 可用"
else
log_warn "Docker Compose 未安装,部分功能可能不可用"
COMPOSE_CMD=""
fi
log_info "Docker 环境检查通过"
}
# -----------------------------------------------------------------------------
# 检查前置要求
# -----------------------------------------------------------------------------
check_requirements() {
log_step "检查前置要求..."
# 检查构建上下文
if [ ! -f "Dockerfile" ]; then
log_error "Dockerfile 不存在,请确保在项目根目录运行此脚本"
exit 1
fi
if [ ! -f "package.json" ]; then
log_error "package.json 不存在,请确保在项目根目录运行此脚本"
exit 1
fi
# 创建必要目录
mkdir -p "$DATA_DIR"
log_info "前置要求检查通过"
}
# -----------------------------------------------------------------------------
# 拉取最新代码(可选)
# -----------------------------------------------------------------------------
pull_latest() {
log_step "检查更新..."
if [ -d ".git" ]; then
git fetch origin main
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse origin/main)
if [ "$LOCAL" != "$REMOTE" ]; then
log_warn "本地版本落后于远程,是否更新?"
read -p "输入 y 更新,其他跳过: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "更新代码..."
git pull origin main
fi
else
log_info "已是最新版本"
fi
fi
}
# -----------------------------------------------------------------------------
# 构建镜像
# -----------------------------------------------------------------------------
build_image() {
log_step "构建 Docker 镜像..."
# 启用 BuildKit
export DOCKER_BUILDKIT=1
# 构建镜像
log_info "构建镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
if docker build \
--tag "${IMAGE_NAME}:${IMAGE_TAG}" \
--tag "${IMAGE_NAME}:latest" \
--build-arg NPM_REGISTRY=https://registry.npmmirror.com \
--progress=plain \
.; then
log_info "镜像构建成功"
else
log_error "镜像构建失败"
exit 1
fi
# 显示镜像大小
IMAGE_SIZE=$(docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "{{.Size}}")
log_info "镜像大小: $IMAGE_SIZE"
}
# -----------------------------------------------------------------------------
# 启动容器
# -----------------------------------------------------------------------------
start_container() {
log_step "启动容器..."
# 检查容器是否已存在
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_warn "容器已在运行中"
return 0
else
log_info "容器已存在,重新启动..."
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
fi
fi
# 检查配置目录
if [ ! -d "$CONFIG_DIR" ]; then
log_warn "OpenClaw 配置目录不存在: $CONFIG_DIR"
log_info "将创建目录..."
mkdir -p "$CONFIG_DIR"
fi
# 启动容器(使用 host 网络模式)
log_info "启动容器 (host 网络模式)..."
docker run -d \
--name "$CONTAINER_NAME" \
--hostname "$CONTAINER_NAME" \
--network host \
--restart unless-stopped \
--volume "$CONFIG_DIR:/root/.openclaw" \
--volume "$DATA_DIR:/app/data" \
--env "NODE_ENV=production" \
--env "OPENCLAW_URL=http://127.0.0.1:18789" \
--env "TZ=Asia/Shanghai" \
--health-cmd "curl -f http://localhost:1420/ || exit 1" \
--health-interval "30s" \
--health-timeout "5s" \
--health-retries "3" \
--log-driver "json-file" \
--log-opt "max-size=10m" \
--log-opt "max-file=3" \
"${IMAGE_NAME}:${IMAGE_TAG}"
if [ $? -eq 0 ]; then
log_info "容器启动成功"
else
log_error "容器启动失败"
exit 1
fi
}
# -----------------------------------------------------------------------------
# 停止容器
# -----------------------------------------------------------------------------
stop_container() {
log_step "停止容器..."
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop "$CONTAINER_NAME"
log_info "容器已停止"
else
log_warn "容器未运行"
fi
}
# -----------------------------------------------------------------------------
# 重启容器
# -----------------------------------------------------------------------------
restart_container() {
log_step "重启容器..."
stop_container
sleep 2
start_container
}
# -----------------------------------------------------------------------------
# 删除容器
# -----------------------------------------------------------------------------
remove_container() {
log_step "删除容器..."
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
log_info "容器已删除"
else
log_warn "容器不存在"
fi
}
# -----------------------------------------------------------------------------
# 查看状态
# -----------------------------------------------------------------------------
show_status() {
log_step "容器状态:"
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo ""
docker ps -a --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 显示资源使用
echo ""
log_info "资源使用:"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" "$CONTAINER_NAME" 2>/dev/null || true
else
log_warn "容器不存在"
fi
}
# -----------------------------------------------------------------------------
# 查看日志
# -----------------------------------------------------------------------------
show_logs() {
log_step "容器日志 (Ctrl+C 退出):"
echo ""
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker logs -f "$CONTAINER_NAME"
else
log_error "容器未运行,无法查看日志"
exit 1
fi
}
# -----------------------------------------------------------------------------
# 进入容器
# -----------------------------------------------------------------------------
enter_container() {
log_step "进入容器 shell..."
echo ""
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker exec -it "$CONTAINER_NAME" /bin/sh
else
log_error "容器未运行,无法进入"
exit 1
fi
}
# -----------------------------------------------------------------------------
# 常见问题排查
# -----------------------------------------------------------------------------
troubleshoot() {
echo ""
separator
log_info "常见问题排查"
separator
echo ""
log_info "1. 检查容器是否运行:"
echo " docker ps | grep $CONTAINER_NAME"
echo ""
log_info "2. 查看容器日志:"
echo " docker logs $CONTAINER_NAME"
echo ""
log_info "3. 检查端口占用:"
echo " netstat -tlnp | grep 1420"
echo " ss -tlnp | grep 1420"
echo ""
log_info "4. 检查 OpenClaw 配置:"
echo " cat ~/.openclaw/openclaw.json"
echo ""
log_info "5. 测试 Gateway 连接:"
echo " curl http://localhost:18789/health"
echo ""
log_info "6. 重建容器:"
echo " ./docker-deploy.sh rebuild"
echo ""
log_info "7. 完全重置:"
echo " docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME"
echo " docker rmi ${IMAGE_NAME}:${IMAGE_TAG}"
echo " ./docker-deploy.sh start"
echo ""
log_info "8. 查看 OpenClaw 日志:"
echo " docker exec $CONTAINER_NAME cat /home/appuser/.openclaw/logs/gateway.log"
echo ""
separator
echo ""
}
# -----------------------------------------------------------------------------
# 获取本机 IP
# -----------------------------------------------------------------------------
get_local_ip() {
ip route get 1 2>/dev/null | awk '{print $7; exit}' || \
hostname -I 2>/dev/null | awk '{print $1}' || \
echo "localhost"
}
# -----------------------------------------------------------------------------
# 显示访问信息
# -----------------------------------------------------------------------------
show_access_info() {
local ip=$(get_local_ip)
echo ""
separator
log_info "部署完成!"
separator
echo ""
echo -e " ${CYAN}🌐 访问地址:${NC}"
echo " http://${ip}:1420"
echo ""
echo -e " ${CYAN}📁 配置目录:${NC}"
echo " $CONFIG_DIR"
echo ""
echo -e " ${CYAN}📋 容器名称:${NC}"
echo " $CONTAINER_NAME"
echo ""
echo " 常用命令:"
echo " ./docker-deploy.sh logs # 查看日志"
echo " ./docker-deploy.sh status # 查看状态"
echo " ./docker-deploy.sh stop # 停止"
echo " ./docker-deploy.sh start # 启动"
echo " ./docker-deploy.sh restart # 重启"
echo " ./docker-deploy.sh rebuild # 重建"
echo " ./docker-deploy.sh shell # 进入容器"
echo " ./docker-deploy.sh help # 帮助"
echo ""
separator
echo ""
}
# -----------------------------------------------------------------------------
# 使用 Docker Compose 方式
# -----------------------------------------------------------------------------
compose_up() {
if [ -z "$COMPOSE_CMD" ]; then
log_error "Docker Compose 不可用,请使用单机模式"
exit 1
fi
log_step "使用 Docker Compose 启动..."
if [ ! -f "docker-compose.yml" ]; then
log_error "docker-compose.yml 不存在"
exit 1
fi
$COMPOSE_CMD up -d
log_info "服务已启动"
}
compose_down() {
if [ -z "$COMPOSE_CMD" ]; then
log_error "Docker Compose 不可用"
exit 1
fi
log_step "停止 Docker Compose 服务..."
$COMPOSE_CMD down
}
# -----------------------------------------------------------------------------
# 显示帮助
# -----------------------------------------------------------------------------
show_help() {
echo ""
echo "ClawPanel Docker 部署脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " start 启动容器"
echo " stop 停止容器"
echo " restart 重启容器"
echo " rebuild 重建容器(删除并重新创建)"
echo " remove 删除容器(保留镜像)"
echo " status 查看容器状态"
echo " logs 查看容器日志"
echo " shell 进入容器 shell"
echo " troubleshoot 常见问题排查"
echo " compose 使用 Docker Compose 方式启动"
echo " help 显示帮助"
echo ""
echo "示例:"
echo " $0 start # 启动容器"
echo " $0 logs -f # 实时查看日志"
echo " $0 rebuild # 重建容器"
echo ""
}
# -----------------------------------------------------------------------------
# 主流程
# -----------------------------------------------------------------------------
main() {
case "${1:-help}" in
check)
check_docker
;;
build)
check_docker
check_requirements
pull_latest
build_image
;;
start)
check_docker
check_requirements
start_container
show_access_info
;;
stop)
stop_container
;;
restart)
restart_container
;;
rebuild)
check_docker
remove_container
build_image
start_container
show_access_info
;;
remove)
remove_container
;;
status)
check_docker
show_status
;;
logs)
show_logs
;;
shell)
enter_container
;;
troubleshoot)
troubleshoot
;;
compose)
check_docker
compose_up
show_access_info
;;
compose-down)
compose_down
;;
help|--help|-h)
show_help
;;
*)
log_error "未知命令: $1"
show_help
exit 1
;;
esac
}
main "$@"

View File

@@ -129,18 +129,69 @@ install_git() {
echo "✅ Git 安装完成"
}
# 查找 openclaw 可执行文件(兼容各种安装方式)
find_openclaw() {
# 1. 直接在 PATH 中查找
if command -v openclaw &> /dev/null; then
echo "$(command -v openclaw)"
return 0
fi
# 2. 常见 npm 全局安装路径
local candidates=(
"/usr/local/bin/openclaw"
"/usr/bin/openclaw"
"$HOME/.npm-global/bin/openclaw"
"$HOME/.local/bin/openclaw"
)
# 3. 从 npm prefix 获取(不使用 sudo避免触发密码提示
local npm_prefix=$(npm config get prefix 2>/dev/null)
if [ -n "$npm_prefix" ]; then
candidates+=("$npm_prefix/bin/openclaw")
fi
for p in "${candidates[@]}"; do
if [ -x "$p" ]; then
echo "$p"
return 0
fi
done
return 1
}
# 检测 OpenClaw 版本来源(官方 vs 汉化版)
detect_openclaw_source() {
local oc_bin="$1"
local ver=$("$oc_bin" --version 2>/dev/null || echo "")
if echo "$ver" | grep -qi "zh\|汉化\|chinese"; then
echo "chinese"
else
echo "official"
fi
}
# 安装 OpenClaw
install_openclaw() {
if command -v openclaw &> /dev/null; then
echo "✅ OpenClaw 已安装: $(openclaw --version 2>/dev/null || echo '未知版本')"
local oc_path=$(find_openclaw)
if [ -n "$oc_path" ]; then
local oc_ver=$("$oc_path" --version 2>/dev/null || echo "未知版本")
local oc_src=$(detect_openclaw_source "$oc_path")
if [ "$oc_src" = "chinese" ]; then
echo "✅ OpenClaw 汉化版已安装: $oc_ver (${oc_path})"
else
echo "✅ OpenClaw 已安装: $oc_ver (${oc_path})"
fi
# 确保 openclaw 在 PATH 中(防止后续步骤找不到)
if ! command -v openclaw &> /dev/null; then
export PATH="$(dirname "$oc_path"):$PATH"
echo " 已将 $(dirname "$oc_path") 加入 PATH"
fi
else
echo "📦 安装 OpenClaw 汉化版..."
if [ "$IS_ROOT" = true ]; then
npm install -g @qingchencloud/openclaw-zh --registry "$NPM_REGISTRY" || \
npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmjs.org
else
sudo npm install -g @qingchencloud/openclaw-zh --registry "$NPM_REGISTRY" || \
sudo npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmjs.org
sudo -E npm install -g @qingchencloud/openclaw-zh --registry "$NPM_REGISTRY" || \
sudo -E npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmjs.org
fi
echo "✅ OpenClaw 安装完成"
fi