feat: add cloud control-plane deployment entrypoints
This commit is contained in:
62
README.md
62
README.md
@@ -24,6 +24,8 @@ Boss 是一个面向多设备开发协作的 agent control plane。
|
|||||||
- Web 控制台
|
- Web 控制台
|
||||||
- `boss-worker` 模拟执行器
|
- `boss-worker` 模拟执行器
|
||||||
- `boss-worker` 外部命令执行模式,可接本地 Codex / Claude / 自定义脚本
|
- `boss-worker` 外部命令执行模式,可接本地 Codex / Claude / 自定义脚本
|
||||||
|
- AI Glasses 云服务器一键部署脚本
|
||||||
|
- 命令行对话入口脚本
|
||||||
- `npm run smoke` 自动跑端到端验证
|
- `npm run smoke` 自动跑端到端验证
|
||||||
- `Dockerfile` + `compose.yaml` 支持容器启动
|
- `Dockerfile` + `compose.yaml` 支持容器启动
|
||||||
|
|
||||||
@@ -60,6 +62,40 @@ npm run dev
|
|||||||
BOSS_DATA_FILE=.boss-data/local-dev.json npm run dev
|
BOSS_DATA_FILE=.boss-data/local-dev.json npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 云端主控部署
|
||||||
|
|
||||||
|
如果你要按最初的产品策略来跑:
|
||||||
|
|
||||||
|
- 云服务器只跑 Boss control plane
|
||||||
|
- 你的 Windows / Mac 本机各自跑 worker
|
||||||
|
- 你通过浏览器或命令行和云端主控对话
|
||||||
|
|
||||||
|
仓库已经内置 AI Glasses 云服务器部署脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/deploy_ai_glasses_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
部署完成后,先用这个脚本确认云端状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/boss_cloud_status.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
查看云端日志:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/boss_cloud_logs.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
默认云端入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http://111.231.132.51:43210
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是你当前最短的“主账号对话入口”。
|
||||||
|
|
||||||
如果你要手工启动 worker:
|
如果你要手工启动 worker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -108,6 +144,30 @@ npm run worker -- \
|
|||||||
--executor ./scripts/claude_executor.sh
|
--executor ./scripts/claude_executor.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 怎么和系统对话
|
||||||
|
|
||||||
|
当前推荐两种方式:
|
||||||
|
|
||||||
|
1. 浏览器入口
|
||||||
|
打开云端控制台:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
http://111.231.132.51:43210
|
||||||
|
```
|
||||||
|
|
||||||
|
这是最符合产品策略的入口,也是主控面。
|
||||||
|
|
||||||
|
2. 命令行聊天入口
|
||||||
|
仓库里自带一个简单 CLI,可以直接发消息给 Boss:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh create "Boss 主控对话"
|
||||||
|
BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh send "先调研这个问题,不要急着改代码。"
|
||||||
|
BOSS_SERVER_URL=http://111.231.132.51:43210 ./scripts/boss_chat.sh status
|
||||||
|
```
|
||||||
|
|
||||||
|
这条 CLI 入口后面也很容易改造成 Telegram / Slack / 企业微信 webhook。
|
||||||
|
|
||||||
一键本地 demo:
|
一键本地 demo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -142,4 +202,6 @@ docker compose up --build
|
|||||||
- 审批、暂停、恢复、取消、重排
|
- 审批、暂停、恢复、取消、重排
|
||||||
- SSE 实时事件流和 Web 控制台
|
- SSE 实时事件流和 Web 控制台
|
||||||
- 会话归档与恢复
|
- 会话归档与恢复
|
||||||
|
- 云端主控部署脚本与状态脚本
|
||||||
|
- 命令行对话入口
|
||||||
- 一键 demo 启动
|
- 一键 demo 启动
|
||||||
|
|||||||
15
compose.cloud.yaml
Normal file
15
compose.cloud.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
boss:
|
||||||
|
build: .
|
||||||
|
container_name: boss-control-plane
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 43210
|
||||||
|
BOSS_DATA_FILE: .boss-data/cloud-store.json
|
||||||
|
ports:
|
||||||
|
- "43210:43210"
|
||||||
|
volumes:
|
||||||
|
- boss-data:/app/.boss-data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
boss-data:
|
||||||
133
scripts/boss_chat.sh
Executable file
133
scripts/boss_chat.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
server_url="${BOSS_SERVER_URL:-http://127.0.0.1:43210}"
|
||||||
|
session_file="${BOSS_SESSION_FILE:-.boss-session}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
boss_chat.sh sessions
|
||||||
|
boss_chat.sh create [title]
|
||||||
|
boss_chat.sh send "<message>" [session_id]
|
||||||
|
boss_chat.sh status [session_id]
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
BOSS_SERVER_URL Default: http://127.0.0.1:43210
|
||||||
|
BOSS_SESSION_FILE Default: .boss-session
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
require_tools() {
|
||||||
|
command -v curl >/dev/null 2>&1 || {
|
||||||
|
echo "curl is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v node >/dev/null 2>&1 || {
|
||||||
|
echo "node is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request() {
|
||||||
|
local method="$1"
|
||||||
|
local path="$2"
|
||||||
|
local body="${3:-}"
|
||||||
|
|
||||||
|
if [[ -n "$body" ]]; then
|
||||||
|
curl -fsS -X "$method" "$server_url$path" -H 'Content-Type: application/json' -d "$body"
|
||||||
|
else
|
||||||
|
curl -fsS -X "$method" "$server_url$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
json_payload() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
node -e 'const [key, value] = process.argv.slice(1); process.stdout.write(JSON.stringify({ [key]: value }));' "$key" "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_message_payload() {
|
||||||
|
local value="$1"
|
||||||
|
node -e 'const value = process.argv[1]; process.stdout.write(JSON.stringify({ content: value, channel: "cli" }));' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_get() {
|
||||||
|
local expr="$1"
|
||||||
|
node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync(0, 'utf8')); const result = (${expr}); if (result === undefined || result === null) process.exit(2); if (typeof result === 'string') process.stdout.write(result); else process.stdout.write(JSON.stringify(result, null, 2));"
|
||||||
|
}
|
||||||
|
|
||||||
|
save_session_id() {
|
||||||
|
printf '%s' "$1" > "$session_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_session_id() {
|
||||||
|
if [[ -f "$session_file" ]]; then
|
||||||
|
cat "$session_file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
create_session() {
|
||||||
|
local title="${1:-Boss 主控对话}"
|
||||||
|
local payload
|
||||||
|
payload="$(request POST /api/sessions "$(json_payload title "$title")")"
|
||||||
|
local session_id
|
||||||
|
session_id="$(printf '%s' "$payload" | json_get "data.session.id")"
|
||||||
|
save_session_id "$session_id"
|
||||||
|
printf '%s\n' "$payload"
|
||||||
|
}
|
||||||
|
|
||||||
|
command="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
if [[ -z "$command" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_tools
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
sessions)
|
||||||
|
request GET /api/sessions | node -e 'const fs=require("fs"); const data=JSON.parse(fs.readFileSync(0,"utf8")); for (const session of data) console.log(`${session.id}\t${session.status}\t${session.title}`);'
|
||||||
|
;;
|
||||||
|
create)
|
||||||
|
create_session "${1:-Boss 主控对话}"
|
||||||
|
;;
|
||||||
|
send)
|
||||||
|
message="${1:-}"
|
||||||
|
if [[ -z "$message" ]]; then
|
||||||
|
echo "Missing message." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
session_id="${2:-}"
|
||||||
|
if [[ -z "$session_id" ]]; then
|
||||||
|
session_id="$(load_session_id || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "$session_id" ]]; then
|
||||||
|
session_json="$(create_session "Boss 主控对话")"
|
||||||
|
session_id="$(printf '%s' "$session_json" | json_get "data.session.id")"
|
||||||
|
fi
|
||||||
|
request POST "/api/sessions/$session_id/messages" "$(json_message_payload "$message")"
|
||||||
|
printf '\n'
|
||||||
|
save_session_id "$session_id"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
session_id="${1:-}"
|
||||||
|
if [[ -z "$session_id" ]]; then
|
||||||
|
session_id="$(load_session_id || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "$session_id" ]]; then
|
||||||
|
echo "No session id found. Run create or send first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
request GET "/api/sessions/$session_id"
|
||||||
|
printf '\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
11
scripts/boss_cloud_logs.sh
Executable file
11
scripts/boss_cloud_logs.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}"
|
||||||
|
tail_lines="${1:-150}"
|
||||||
|
|
||||||
|
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||||
|
export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}"
|
||||||
|
export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}"
|
||||||
|
|
||||||
|
"$AG_SERVER" exec "set -euo pipefail; cd $(printf '%q' "$remote_dir"); sudo docker compose -f compose.cloud.yaml -p boss logs --tail $(printf '%q' "$tail_lines")"
|
||||||
10
scripts/boss_cloud_status.sh
Executable file
10
scripts/boss_cloud_status.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}"
|
||||||
|
|
||||||
|
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||||
|
export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}"
|
||||||
|
export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}"
|
||||||
|
|
||||||
|
"$AG_SERVER" exec "set -euo pipefail; cd $(printf '%q' "$remote_dir"); sudo docker compose -f compose.cloud.yaml -p boss ps; echo '---'; curl -fsS http://127.0.0.1:43210/api/health"
|
||||||
46
scripts/deploy_ai_glasses_server.sh
Executable file
46
scripts/deploy_ai_glasses_server.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
branch="${1:-main}"
|
||||||
|
remote_dir="${BOSS_REMOTE_DIR:-/home/ubuntu/boss}"
|
||||||
|
remote_repo="${BOSS_REMOTE_REPO:-https://git.hyzq.site/krisolo/boss.git}"
|
||||||
|
|
||||||
|
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
||||||
|
export AG_SERVER_SKILL="${AG_SERVER_SKILL:-$CODEX_HOME/skills/ai-glasses-server-debug}"
|
||||||
|
export AG_SERVER="${AG_SERVER:-$AG_SERVER_SKILL/scripts/server_ssh.sh}"
|
||||||
|
|
||||||
|
if [[ ! -x "$AG_SERVER" ]]; then
|
||||||
|
echo "AI Glasses server wrapper not found: $AG_SERVER" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_script=$(cat <<EOF
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REMOTE_DIR=$(printf '%q' "$remote_dir")
|
||||||
|
REMOTE_REPO=$(printf '%q' "$remote_repo")
|
||||||
|
BRANCH=$(printf '%q' "$branch")
|
||||||
|
|
||||||
|
mkdir -p "\$REMOTE_DIR"
|
||||||
|
|
||||||
|
if [[ ! -d "\$REMOTE_DIR/.git" ]]; then
|
||||||
|
rm -rf "\$REMOTE_DIR"
|
||||||
|
git clone "\$REMOTE_REPO" "\$REMOTE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "\$REMOTE_DIR"
|
||||||
|
git fetch origin "\$BRANCH"
|
||||||
|
git checkout "\$BRANCH"
|
||||||
|
git reset --hard "origin/\$BRANCH"
|
||||||
|
|
||||||
|
sudo docker compose -f compose.cloud.yaml -p boss up -d --build --remove-orphans
|
||||||
|
|
||||||
|
echo "__BOSS_DEPLOY_OK__"
|
||||||
|
sudo docker compose -f compose.cloud.yaml -p boss ps
|
||||||
|
sleep 3
|
||||||
|
curl -fsS http://127.0.0.1:43210/api/health
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
payload="$(printf '%s' "$remote_script" | base64 | tr -d '\n')"
|
||||||
|
"$AG_SERVER" exec "set -euo pipefail; printf '%s' '$payload' | base64 -d | bash"
|
||||||
Reference in New Issue
Block a user