Spaces:
Running
Running
| set -e | |
| echo "🤖 MoltBot AI - Starting..." | |
| # ============================================ | |
| # 从环境变量生成 OpenClaw 配置 | |
| # ============================================ | |
| FEISHU_APP_ID="${FEISHU_APP_ID:-}" | |
| FEISHU_APP_SECRET="${FEISHU_APP_SECRET:-}" | |
| API_BASE_URL="${API_BASE_URL:-https://asem12345-cliproxyapi.hf.space/v1}" | |
| API_KEY="${API_KEY:-}" | |
| MODEL_NAME="${MODEL_NAME:-gemini-3-flash}" | |
| BRAVE_API_KEY="${BRAVE_API_KEY:-}" | |
| if [ -z "$FEISHU_APP_ID" ] || [ -z "$FEISHU_APP_SECRET" ]; then | |
| echo "❌ 错误: 请设置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量" | |
| echo " 在 HF Space Settings → Secrets 中添加" | |
| exit 1 | |
| fi | |
| echo "📝 生成 OpenClaw 配置..." | |
| # 生成 provider ID:从 URL 提取域名部分,加 custom- 前缀 | |
| # 例如 https://asem12345-cliproxyapi.hf.space/v1 → custom-asem12345-cliproxyapi-hf-space | |
| PROVIDER_ID="custom-$(echo "$API_BASE_URL" | sed 's|https\?://||' | sed 's|/.*||' | sed 's|[^a-zA-Z0-9]|-|g' | sed 's|-*$||')" | |
| OPENCLAW_DIR="$HOME/.openclaw" | |
| # 创建必要目录 | |
| mkdir -p "$OPENCLAW_DIR/agents/main/sessions" | |
| mkdir -p "$OPENCLAW_DIR/workspace" | |
| chmod 700 "$OPENCLAW_DIR" 2>/dev/null || true | |
| # 先写一个最小配置让 doctor 能跑 | |
| cat > "$OPENCLAW_DIR/openclaw.json" << JSONEOF | |
| { | |
| "gateway": { | |
| "port": 18789, | |
| "bind": "loopback", | |
| "mode": "local" | |
| }, | |
| "channels": { | |
| "feishu": { | |
| "enabled": false | |
| } | |
| } | |
| } | |
| JSONEOF | |
| echo "✅ 最小配置已生成" | |
| echo " 飞书 App ID: ${FEISHU_APP_ID}" | |
| # ============================================ | |
| # 运行 doctor --fix(自动安装飞书插件等) | |
| # ============================================ | |
| echo "🔧 运行 doctor --fix..." | |
| openclaw doctor --fix || true | |
| # 强力删除飞书插件,防止自动启用 | |
| rm -rf /root/.openclaw/extensions/feishu-openclaw | |
| # ============================================ | |
| # doctor 完成后,写入完整配置(包含自定义模型) | |
| # doctor 有时会覆盖我们的配置,所以放在 doctor 之后 | |
| # ============================================ | |
| echo "📝 写入完整配置..." | |
| # 用 python 合并配置(保留 doctor 添加的字段如 meta, wizard, plugins 等) | |
| python3 << PYEOF | |
| import json, os | |
| config_path = os.path.expanduser("~/.openclaw/openclaw.json") | |
| # 读取 doctor 生成的配置 | |
| try: | |
| with open(config_path) as f: | |
| config = json.load(f) | |
| except: | |
| config = {} | |
| # 设置 gateway | |
| config.setdefault("gateway", {}) | |
| config["gateway"]["port"] = 18789 | |
| config["gateway"]["bind"] = "loopback" | |
| config["gateway"]["mode"] = "local" | |
| # 设置自定义 provider | |
| config.setdefault("models", {}) | |
| config["models"]["mode"] = "merge" | |
| config["models"].setdefault("providers", {}) | |
| config["models"]["providers"]["${PROVIDER_ID}"] = { | |
| "baseUrl": "${API_BASE_URL}", | |
| "apiKey": "${API_KEY}", | |
| "api": "openai-completions", | |
| "models": [ | |
| { | |
| "id": "${MODEL_NAME}", | |
| "name": "${MODEL_NAME} (Custom Provider)", | |
| "reasoning": False, | |
| "input": ["text"], | |
| "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, | |
| "contextWindow": 131072, | |
| "maxTokens": 8192 | |
| } | |
| ] | |
| } | |
| # 设置 agent defaults | |
| config.setdefault("agents", {}).setdefault("defaults", {}) | |
| config["agents"]["defaults"]["model"] = { | |
| "primary": "${PROVIDER_ID}/${MODEL_NAME}" | |
| } | |
| # 配置 imageModel(Gemini 原生支持多模态,复用主模型) | |
| config["agents"]["defaults"]["imageModel"] = { | |
| "primary": "${PROVIDER_ID}/${MODEL_NAME}" | |
| } | |
| config["agents"]["defaults"].setdefault("models", {}) | |
| config["agents"]["defaults"]["models"]["${PROVIDER_ID}/${MODEL_NAME}"] = {} | |
| config["agents"]["defaults"].setdefault("workspace", os.path.expanduser("~/.openclaw/workspace")) | |
| config["agents"]["defaults"].setdefault("compaction", {"mode": "safeguard"}) | |
| config["agents"]["defaults"].setdefault("maxConcurrent", 4) | |
| # 配置 memory(记忆功能) | |
| config["agents"]["defaults"]["memorySearch"] = { | |
| "enabled": True, | |
| "provider": "local" | |
| } | |
| print(f"✅ 图片分析(imageModel)已启用") | |
| print(f"✅ 记忆功能(memory)已启用") | |
| # 删除飞书 channel 配置 (防止 OpenClaw 自动启用) | |
| config.setdefault("channels", {}) | |
| if "feishu" in config["channels"]: | |
| del config["channels"]["feishu"] | |
| # 配置 Brave Search(上网搜索) | |
| brave_key = os.environ.get("BRAVE_API_KEY", "${BRAVE_API_KEY}") | |
| if brave_key: | |
| config.setdefault("tools", {}).setdefault("web", {}) | |
| config["tools"]["web"]["search"] = { | |
| "enabled": True, | |
| "provider": "brave", | |
| "maxResults": 10 | |
| } | |
| print(f"✅ Brave Search 已启用") | |
| with open(config_path, "w") as f: | |
| json.dump(config, f, indent=2) | |
| print(f"✅ 完整配置已写入 {config_path}") | |
| print(f" 模型: ${PROVIDER_ID}/${MODEL_NAME}") | |
| # 同时写入 agent 级别的 models.json(防止 fallback 到 anthropic) | |
| agent_dir = os.path.expanduser("~/.openclaw/agents/main/agent") | |
| os.makedirs(agent_dir, exist_ok=True) | |
| agent_models = { | |
| "providers": { | |
| "github-copilot": { | |
| "baseUrl": "https://api.individual.githubcopilot.com", | |
| "models": [] | |
| }, | |
| "${PROVIDER_ID}": { | |
| "baseUrl": "${API_BASE_URL}", | |
| "apiKey": "${API_KEY}", | |
| "api": "openai-completions", | |
| "models": [ | |
| { | |
| "id": "${MODEL_NAME}", | |
| "name": "${MODEL_NAME} (Custom Provider)", | |
| "reasoning": False, | |
| "input": ["text"], | |
| "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}, | |
| "contextWindow": 131072, | |
| "maxTokens": 8192 | |
| } | |
| ] | |
| } | |
| } | |
| } | |
| models_path = os.path.join(agent_dir, "models.json") | |
| with open(models_path, "w") as f: | |
| json.dump(agent_models, f, indent=2) | |
| # 确保 auth-profiles.json 存在(OpenClaw 查找的是这个文件) | |
| auth_path = os.path.join(agent_dir, "auth-profiles.json") | |
| with open(auth_path, "w") as f: | |
| json.dump({}, f) | |
| # 也写一份 auth.json 以防万一 | |
| auth2_path = os.path.join(agent_dir, "auth.json") | |
| if not os.path.exists(auth2_path): | |
| with open(auth2_path, "w") as f: | |
| json.dump({}, f) | |
| print(f"✅ Agent 配置已写入 {agent_dir}") | |
| # 递归扫描所有 json 文件,替换 anthropic 引用 | |
| import glob | |
| replaced = [] | |
| provider_id = "${PROVIDER_ID}" | |
| model_id = "${MODEL_NAME}" | |
| full_model = f"{provider_id}/{model_id}" | |
| for fpath in glob.glob(os.path.expanduser("~/.openclaw/**/*.json"), recursive=True): | |
| try: | |
| with open(fpath) as f: | |
| content = f.read() | |
| if "anthropic" in content: | |
| original = content | |
| # 替换 model references | |
| content = content.replace('"anthropic/claude-sonnet-4-20250514"', f'"{full_model}"') | |
| content = content.replace('"anthropic/claude-3-5-sonnet"', f'"{full_model}"') | |
| content = content.replace('"anthropic/claude-3-5-haiku"', f'"{full_model}"') | |
| content = content.replace('"anthropic/claude-3-haiku"', f'"{full_model}"') | |
| # 通用 anthropic provider 引用 | |
| content = content.replace('"anthropic"', f'"{provider_id}"') | |
| if content != original: | |
| with open(fpath, "w") as f: | |
| f.write(content) | |
| replaced.append(fpath) | |
| except: | |
| pass | |
| if replaced: | |
| print(f"⚠️ 替换了 {len(replaced)} 个文件中的 anthropic 引用:") | |
| for r in replaced: | |
| print(f" - {r}") | |
| else: | |
| print("✅ 未发现 anthropic 引用") | |
| # 打印调试信息 | |
| print("\n🔍 调试 - openclaw.json:") | |
| try: | |
| with open(config_path) as f: | |
| print(f.read()[:2000]) | |
| except: | |
| print(" 无法读取") | |
| print("\n🔍 调试 - agent 目录内容:") | |
| for fpath in glob.glob(os.path.join(agent_dir, "*")): | |
| print(f" {fpath}") | |
| try: | |
| with open(fpath) as f: | |
| c = f.read()[:500] | |
| print(f" {c}") | |
| except: | |
| pass | |
| PYEOF | |
| # 注意: 不再使用 image_proxy 和 sed patch(会破坏 WebSocket 连接) | |
| # 图片处理完全由 image_daemon.py 负责 | |
| # ============================================ | |
| # 启动 OpenClaw Gateway(后台) | |
| # ============================================ | |
| echo "🚀 启动 OpenClaw Gateway..." | |
| openclaw gateway --force & | |
| GATEWAY_PID=$! | |
| echo " Gateway PID: $GATEWAY_PID" | |
| # 等待网关启动 | |
| sleep 5 | |
| # ============================================ | |
| # 启动图片预处理守护进程(后台,带自动重启守护) | |
| # ============================================ | |
| echo "🖼️ 启动图片预处理守护进程(带自动重启)..." | |
| ( | |
| RESTART_COUNT=0 | |
| while true; do | |
| RESTART_COUNT=$((RESTART_COUNT + 1)) | |
| echo "[image_daemon_guard] 🚀 启动 image_daemon (第 ${RESTART_COUNT} 次)" | |
| python3 /app/image_daemon.py | |
| EXIT_CODE=$? | |
| echo "[image_daemon_guard] ⚠️ image_daemon 退出 (code=${EXIT_CODE}), 3 秒后重启..." | |
| sleep 3 | |
| done | |
| ) & | |
| IMAGE_DAEMON_PID=$! | |
| echo " Image Daemon Guard PID: $IMAGE_DAEMON_PID" | |
| # ============================================ | |
| # 启动状态监控网页(前台,端口 7860) | |
| # ============================================ | |
| echo "📊 启动状态监控网页 (端口 7860)..." | |
| exec python3 /app/status_page.py | |