| #!/bin/bash |
| set -e |
|
|
| echo "🤖 MoltBot AI - Starting..." |
|
|
| |
| |
| |
| 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="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 |
|
|
| |
| 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}" |
|
|
| |
| |
| |
| echo "🔧 运行 doctor --fix..." |
| openclaw doctor --fix || true |
| |
| rm -rf /root/.openclaw/extensions/feishu-openclaw |
|
|
| |
| |
| |
| |
| echo "📝 写入完整配置..." |
|
|
| |
| 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 |
|
|
| |
| |
|
|
| |
| |
| |
| 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" |
|
|
| |
| |
| |
| echo "📊 启动状态监控网页 (端口 7860)..." |
| exec python3 /app/status_page.py |
|
|
|
|
|
|
|
|