#!/bin/bash # ══════════════════════════════════════════════════════════════ # 三省六部 · OpenClaw Multi-Agent System 一键安装脚本 # ══════════════════════════════════════════════════════════════ set -e REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OC_HOME="$HOME/.openclaw" OC_CFG="$OC_HOME/openclaw.json" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' banner() { echo "" echo -e "${BLUE}╔══════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ 🏛️ 三省六部 · OpenClaw Multi-Agent ║${NC}" echo -e "${BLUE}║ 安装向导 ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════╝${NC}" echo "" } log() { echo -e "${GREEN}✅ $1${NC}"; } warn() { echo -e "${YELLOW}⚠️ $1${NC}"; } error() { echo -e "${RED}❌ $1${NC}"; } info() { echo -e "${BLUE}ℹ️ $1${NC}"; } # ── Step 0: 依赖检查 ────────────────────────────────────────── check_deps() { info "检查依赖..." if ! command -v openclaw &>/dev/null; then error "未找到 openclaw CLI。请先安装 OpenClaw: https://openclaw.ai" exit 1 fi log "OpenClaw CLI: $(openclaw --version 2>/dev/null || echo 'OK')" if ! command -v python3 &>/dev/null; then error "未找到 python3" exit 1 fi log "Python3: $(python3 --version)" if [ ! -f "$OC_CFG" ]; then error "未找到 openclaw.json。请先运行 openclaw 完成初始化。" exit 1 fi log "openclaw.json: $OC_CFG" } # ── Step 0.5: 备份已有 Agent 数据 ────────────────────────────── backup_existing() { AGENTS_DIR="$OC_HOME" BACKUP_DIR="$OC_HOME/backups/pre-install-$(date +%Y%m%d-%H%M%S)" HAS_EXISTING=false # 检查是否有已存在的 workspace for d in "$AGENTS_DIR"/workspace-*/; do if [ -d "$d" ]; then HAS_EXISTING=true break fi done if $HAS_EXISTING; then info "检测到已有 Agent Workspace,自动备份中..." mkdir -p "$BACKUP_DIR" # 备份所有 workspace 目录 for d in "$AGENTS_DIR"/workspace-*/; do if [ -d "$d" ]; then ws_name=$(basename "$d") cp -R "$d" "$BACKUP_DIR/$ws_name" fi done # 备份 openclaw.json if [ -f "$OC_CFG" ]; then cp "$OC_CFG" "$BACKUP_DIR/openclaw.json" fi # 备份 agents 目录(agent 注册信息) if [ -d "$AGENTS_DIR/agents" ]; then cp -R "$AGENTS_DIR/agents" "$BACKUP_DIR/agents" fi log "已备份到: $BACKUP_DIR" info "如需恢复,运行: cp -R $BACKUP_DIR/workspace-* $AGENTS_DIR/" fi } # ── Step 1: 创建 Workspace ────────────────────────────────── create_workspaces() { info "创建 Agent Workspace..." AGENTS=(taizi zhongshu menxia shangshu hubu libu bingbu xingbu gongbu libu_hr zaochao) for agent in "${AGENTS[@]}"; do ws="$OC_HOME/workspace-$agent" mkdir -p "$ws/skills" if [ -f "$REPO_DIR/agents/$agent/SOUL.md" ]; then if [ -f "$ws/SOUL.md" ]; then # 已存在的 SOUL.md,先备份再覆盖 cp "$ws/SOUL.md" "$ws/SOUL.md.bak.$(date +%Y%m%d-%H%M%S)" warn "已备份旧 SOUL.md → $ws/SOUL.md.bak.*" fi sed "s|__REPO_DIR__|$REPO_DIR|g" "$REPO_DIR/agents/$agent/SOUL.md" > "$ws/SOUL.md" fi log "Workspace 已创建: $ws" done # 通用 AGENTS.md(工作协议) for agent in "${AGENTS[@]}"; do cat > "$OC_HOME/workspace-$agent/AGENTS.md" << 'AGENTS_EOF' # AGENTS.md · 工作协议 1. 接到任务先回复"已接旨"。 2. 输出必须包含:任务ID、结果、证据/文件路径、阻塞项。 3. 需要协作时,回复尚书省请求转派,不跨部直连。 4. 涉及删除/外发动作必须明确标注并等待批准。 AGENTS_EOF done } # ── Step 2: 注册 Agents ───────────────────────────────────── register_agents() { info "注册三省六部 Agents..." # 备份配置 cp "$OC_CFG" "$OC_CFG.bak.sansheng-$(date +%Y%m%d-%H%M%S)" log "已备份配置: $OC_CFG.bak.*" python3 << 'PYEOF' import json, pathlib, sys cfg_path = pathlib.Path.home() / '.openclaw' / 'openclaw.json' cfg = json.loads(cfg_path.read_text()) AGENTS = [ {"id": "taizi", "subagents": {"allowAgents": ["zhongshu"]}}, {"id": "zhongshu", "subagents": {"allowAgents": ["menxia", "shangshu"]}}, {"id": "menxia", "subagents": {"allowAgents": ["shangshu", "zhongshu"]}}, {"id": "shangshu", "subagents": {"allowAgents": ["zhongshu", "menxia", "hubu", "libu", "bingbu", "xingbu", "gongbu", "libu_hr"]}}, {"id": "hubu", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "libu", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "bingbu", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "xingbu", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "gongbu", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "libu_hr", "subagents": {"allowAgents": ["shangshu"]}}, {"id": "zaochao", "subagents": {"allowAgents": []}}, ] agents_cfg = cfg.setdefault('agents', {}) agents_list = agents_cfg.get('list', []) existing_ids = {a['id'] for a in agents_list} added = 0 for ag in AGENTS: ag_id = ag['id'] ws = str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}') if ag_id not in existing_ids: entry = {'id': ag_id, 'workspace': ws, **{k:v for k,v in ag.items() if k!='id'}} agents_list.append(entry) added += 1 print(f' + added: {ag_id}') else: print(f' ~ exists: {ag_id} (skipped)') agents_cfg['list'] = agents_list cfg_path.write_text(json.dumps(cfg, ensure_ascii=False, indent=2)) print(f'Done: {added} agents added') PYEOF log "Agents 注册完成" } # ── Step 3: 初始化 Data ───────────────────────────────────── init_data() { info "初始化数据目录..." mkdir -p "$REPO_DIR/data" # 初始化空文件 for f in live_status.json agent_config.json model_change_log.json; do if [ ! -f "$REPO_DIR/data/$f" ]; then echo '{}' > "$REPO_DIR/data/$f" fi done echo '[]' > "$REPO_DIR/data/pending_model_changes.json" # 初始任务文件 if [ ! -f "$REPO_DIR/data/tasks_source.json" ]; then python3 << 'PYEOF' import json, pathlib tasks = [ { "id": "JJC-DEMO-001", "title": "🎉 系统初始化完成", "official": "工部尚书", "org": "工部", "state": "Done", "now": "三省六部系统已就绪", "eta": "-", "block": "无", "output": "", "ac": "系统正常运行", "flow_log": [ {"at": "2024-01-01T00:00:00Z", "from": "皇上", "to": "中书省", "remark": "下旨初始化三省六部系统"}, {"at": "2024-01-01T00:01:00Z", "from": "中书省", "to": "门下省", "remark": "规划方案提交审核"}, {"at": "2024-01-01T00:02:00Z", "from": "门下省", "to": "尚书省", "remark": "✅ 准奏"}, {"at": "2024-01-01T00:03:00Z", "from": "尚书省", "to": "工部", "remark": "派发:系统初始化"}, {"at": "2024-01-01T00:04:00Z", "from": "工部", "to": "尚书省", "remark": "✅ 完成"}, ] } ] p = pathlib.Path(__file__).parent if '__file__' in dir() else pathlib.Path('.') # Write to data dir import os data_dir = pathlib.Path(os.environ.get('REPO_DIR', '.')) / 'data' data_dir.mkdir(exist_ok=True) (data_dir / 'tasks_source.json').write_text(json.dumps(tasks, ensure_ascii=False, indent=2)) print('tasks_source.json 已初始化') PYEOF fi log "数据目录初始化完成: $REPO_DIR/data" } # ── Step 4: 构建前端 ────────────────────────────────────────── build_frontend() { info "构建 React 前端..." if ! command -v node &>/dev/null; then warn "未找到 node,跳过前端构建。看板将使用预构建版本(如果存在)" warn "请安装 Node.js 18+ 后运行: cd edict/frontend && npm install && npm run build" return fi if [ -f "$REPO_DIR/edict/frontend/package.json" ]; then cd "$REPO_DIR/edict/frontend" npm install --silent 2>/dev/null || npm install npm run build 2>/dev/null cd "$REPO_DIR" if [ -f "$REPO_DIR/dashboard/dist/index.html" ]; then log "前端构建完成: dashboard/dist/" else warn "前端构建可能失败,请手动检查" fi else warn "未找到 edict/frontend/package.json,跳过前端构建" fi } # ── Step 5: 首次数据同步 ──────────────────────────────────── first_sync() { info "执行首次数据同步..." cd "$REPO_DIR" REPO_DIR="$REPO_DIR" python3 scripts/sync_agent_config.py || warn "sync_agent_config 有警告" python3 scripts/refresh_live_data.py || warn "refresh_live_data 有警告" log "首次同步完成" } # ── Step 6: 重启 Gateway ──────────────────────────────────── restart_gateway() { info "重启 OpenClaw Gateway..." if openclaw gateway restart 2>/dev/null; then log "Gateway 重启成功" else warn "Gateway 重启失败,请手动重启:openclaw gateway restart" fi } # ── Main ──────────────────────────────────────────────────── banner check_deps backup_existing create_workspaces register_agents init_data build_frontend first_sync restart_gateway echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ 🎉 三省六部安装完成! ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════╝${NC}" echo "" echo "下一步:" echo " 1. 启动数据刷新循环: bash scripts/run_loop.sh &" echo " 2. 启动看板服务器: python3 dashboard/server.py" echo " 3. 打开看板: http://127.0.0.1:7891" echo "" info "文档: docs/getting-started.md"