From fabbd4e347b6dd55215a1b28f38a6a8bf493cf8c Mon Sep 17 00:00:00 2001 From: Shirong Lu <73147033+happysnaker@users.noreply.github.com> Date: Fri, 3 Jul 2026 07:21:50 +0800 Subject: [PATCH] Add qq-ai-bot service --- .gitignore | 4 + app/src/serviceMetadata.ts | 8 ++ docs/2.3.93-Satellite-qq-ai-bot.md | 147 ++++++++++++++++++++++++++++ profiles/default.env | 33 +++++++ services/compose.qq-ai-bot.yml | 61 ++++++++++++ services/qq-ai-bot/group-rules.json | 17 ++++ services/qq-ai-bot/override.env | 6 ++ 7 files changed, 276 insertions(+) create mode 100644 docs/2.3.93-Satellite-qq-ai-bot.md create mode 100644 services/compose.qq-ai-bot.yml create mode 100644 services/qq-ai-bot/group-rules.json create mode 100644 services/qq-ai-bot/override.env diff --git a/.gitignore b/.gitignore index 190442b2d..ac5f99d40 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,10 @@ services/webui/config.json !services/webui/start_webui.sh !services/webui/override.env +# qq-ai-bot +services/qq-ai-bot/data/ + + # Open WebUI (new, in root) webui/* webui/config.json diff --git a/app/src/serviceMetadata.ts b/app/src/serviceMetadata.ts index f087c1623..c273b302c 100644 --- a/app/src/serviceMetadata.ts +++ b/app/src/serviceMetadata.ts @@ -1113,4 +1113,12 @@ export const serviceMetadata: Record> = { wikiUrl: `${wikiUrl}/2.3.92-Satellite-Daytona`, tooltip: 'Self-hosted sandbox platform for AI agents with Docker-in-Docker, computer use, and GPU support.', }, + 'qq-ai-bot': { + name: 'qq-ai-bot', + tags: [HST.satellite, HST.api, HST.tools], + projectUrl: 'https://github.com/happysnaker/qq-ai-bot', + logo: 'https://github.com/happysnaker.png?size=200', + wikiUrl: `${wikiUrl}/2.3.93-Satellite-qq-ai-bot`, + tooltip: 'Self-hosted QQ ↔ AI bot scaffold for OneBot 11 / NapCat / LLOneBot with ACP-compatible agents, persistent sessions, progress streaming, and a Docker demo.', + }, }; diff --git a/docs/2.3.93-Satellite-qq-ai-bot.md b/docs/2.3.93-Satellite-qq-ai-bot.md new file mode 100644 index 000000000..c6ac6af92 --- /dev/null +++ b/docs/2.3.93-Satellite-qq-ai-bot.md @@ -0,0 +1,147 @@ +### [qq-ai-bot](https://github.com/happysnaker/qq-ai-bot) + +> Handle: `qq-ai-bot`
+> Status API: [http://localhost:35020/readyz](http://localhost:35020/readyz)
+> Reverse WebSocket: `ws://localhost:35021/onebot/v11/ws` + +qq-ai-bot is a self-hosted QQ ↔ AI bridge for OneBot 11 adapters such as NapCat and LLOneBot. It focuses on the operational glue between QQ messaging and ACP-compatible agents: persistent sessions, progress streaming, group policies, command handling, and a minimal Docker demo that is easy to wire into an existing self-hosted stack. + +This Harbor service is useful when you already have a QQ / OneBot adapter and want a cleaner AI-facing bridge instead of building session management, progress updates, and QQ command routing from scratch. + +## Starting + +```bash +harbor pull qq-ai-bot +harbor up qq-ai-bot +``` + +By default Harbor starts `qq-ai-bot` with the upstream Docker image and the built-in mock ACP agent command: + +```bash +ACP_AGENT_COMMAND=node +ACP_AGENT_ARGS_JSON=["dist/examples/mock-acp-agent.js"] +ACP_AGENT_WORKDIR=/app +``` + +That means the service can boot and respond on its admin endpoints immediately, even before you wire it to a real OneBot adapter. + +Useful first checks: + +```bash +curl http://localhost:35020/healthz +curl http://localhost:35020/readyz +curl http://localhost:35020/metrics +``` + +Then point your QQ / OneBot implementation at Harbor's reverse WebSocket endpoint: + +```text +ws://localhost:35021/onebot/v11/ws +``` + +If you use an access token, keep the token consistent between the adapter and `HARBOR_QQ_AI_BOT_ONEBOT_ACCESS_TOKEN`. + +## Configuration + +### Environment Variables + +Following options can be set via [`harbor config`](./3.-Harbor-CLI-Reference.md#harbor-config): + +```bash +HARBOR_QQ_AI_BOT_HOST_PORT # Host port for the HTTP admin/status endpoints +HARBOR_QQ_AI_BOT_REVERSE_WS_HOST_PORT # Host port for the OneBot reverse WebSocket listener +HARBOR_QQ_AI_BOT_IMAGE # Docker image +HARBOR_QQ_AI_BOT_VERSION # Docker image tag +HARBOR_QQ_AI_BOT_WORKSPACE # Persistent Harbor workspace +HARBOR_QQ_AI_BOT_OPEN_URL # URL opened by `harbor open qq-ai-bot` +HARBOR_QQ_AI_BOT_SESSION_STORE # file or redis +HARBOR_QQ_AI_BOT_SESSION_TTL_MINUTES # Session TTL in minutes +HARBOR_QQ_AI_BOT_REDIS_URL # Redis connection string when session store is redis +HARBOR_QQ_AI_BOT_REDIS_KEY_PREFIX # Redis key prefix +HARBOR_QQ_AI_BOT_ONEBOT_ACCESS_TOKEN # Shared token for the OneBot reverse WebSocket +HARBOR_QQ_AI_BOT_ALLOW_GROUP # Enable group messages +HARBOR_QQ_AI_BOT_REQUIRE_MENTION_IN_GROUP # Require @ mention in groups +HARBOR_QQ_AI_BOT_ALLOW_PRIVATE # Enable private chats +HARBOR_QQ_AI_BOT_ALLOW_GROUP_COMMANDS_WITHOUT_MENTION +HARBOR_QQ_AI_BOT_COMMAND_PREFIX # QQ command prefix, default / +HARBOR_QQ_AI_BOT_PROGRESS_MODE # off or message +HARBOR_QQ_AI_BOT_OUTBOUND_MAX_TEXT_LENGTH # Max text length for outbound QQ messages +HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_WINDOW_MS # Inbound dedupe window +HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_MAX_ENTRIES +HARBOR_QQ_AI_BOT_ACP_AGENT_COMMAND # Command used to start the ACP-compatible agent +HARBOR_QQ_AI_BOT_ACP_AGENT_ARGS_JSON # JSON string array of ACP agent args +HARBOR_QQ_AI_BOT_ACP_AGENT_WORKDIR # ACP agent working directory +HARBOR_QQ_AI_BOT_ACP_REUSE_SESSION # Reuse remote ACP sessions +HARBOR_QQ_AI_BOT_DEFAULT_SYSTEM_PROMPT # Default system prompt when no group override is set +HARBOR_QQ_AI_BOT_ACP_VERBOSE_MODE # normal / verbose / debug +HARBOR_QQ_AI_BOT_ACP_PERMISSION_STRATEGY # allow_once / allow_always / cancel +HARBOR_QQ_AI_BOT_ACP_PROGRESS_THROTTLE_MS # Progress throttling interval +HARBOR_QQ_AI_BOT_ACP_MAX_PROGRESS_UPDATES # Max progress updates per interaction +HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGES # Max inbound images per interaction +HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGE_BYTES +``` + +### Service-native Environment + +Upstream-native env vars can be written through `harbor env qq-ai-bot` or by editing `services/qq-ai-bot/override.env` directly. + +Examples: + +```bash +harbor env qq-ai-bot ACP_AGENT_COMMAND traecli +harbor env qq-ai-bot ACP_AGENT_ARGS_JSON '["acp","serve"]' +harbor env qq-ai-bot ACP_AGENT_WORKDIR /workspace +``` + +This is the easiest way to replace the built-in mock ACP agent with your real local agent runtime. + +### Volumes + +| Mount | Description | +|-------|-------------| +| `${HARBOR_QQ_AI_BOT_WORKSPACE}/data:/app/data` | Persistent session storage and local runtime data | +| `./services/qq-ai-bot/group-rules.json:/app/config/group-rules.json:ro` | Harbor-managed default group policy file | + +The default group policy file gives you a concrete example of per-group prompts and mention rules. Edit it if you want Harbor's default QQ behavior to match your own deployment assumptions. + +## Typical deployment shape + +A practical self-hosted path looks like this: + +1. run `qq-ai-bot` in Harbor +2. run your QQ adapter separately (for example NapCat or LLOneBot) +3. point the adapter's reverse WebSocket target at `ws://host:35021/onebot/v11/ws` +4. point `qq-ai-bot` at your ACP-compatible agent runtime + +The Harbor service intentionally does **not** bundle a QQ adapter. That keeps the service focused on the AI bridge layer instead of trying to hide NapCat / LLOneBot login or device-state concerns inside Harbor. + +## Operational endpoints + +qq-ai-bot exposes three especially useful admin endpoints: + +- `/healthz` — lightweight liveness check +- `/readyz` — structured runtime status, build info, session store mode, and wiring state +- `/metrics` — Prometheus-style runtime counters + +These make the service more production-friendly than a thin message forwarder and are the main reason it fits Harbor as a reusable satellite service. + +## Troubleshooting + +```bash +harbor logs qq-ai-bot +``` + +Common checks: + +- `curl http://localhost:35020/readyz` to confirm the service is up +- confirm your OneBot reverse WebSocket target is `ws://localhost:35021/onebot/v11/ws` +- if group chat replies seem missing, verify `HARBOR_QQ_AI_BOT_REQUIRE_MENTION_IN_GROUP` +- if ACP calls fail, inspect `ACP_AGENT_COMMAND`, `ACP_AGENT_ARGS_JSON`, and `ACP_AGENT_WORKDIR` +- if you switch to Redis sessions, verify `HARBOR_QQ_AI_BOT_REDIS_URL` and `HARBOR_QQ_AI_BOT_SESSION_STORE=redis` + +## Links + +- [Project page](https://happysnaker.github.io/qq-ai-bot/) +- [GitHub Repository](https://github.com/happysnaker/qq-ai-bot) +- [Docker quickstart](https://github.com/happysnaker/qq-ai-bot/blob/main/docs/docker-quickstart.md) +- [Configuration reference](https://github.com/happysnaker/qq-ai-bot/blob/main/docs/configuration.md) diff --git a/profiles/default.env b/profiles/default.env index d4efe78f2..6cd3ba987 100644 --- a/profiles/default.env +++ b/profiles/default.env @@ -1739,3 +1739,36 @@ HARBOR_DAYTONA_SSH_GATEWAY_PRIVATE_KEY="" HARBOR_DAYTONA_SSH_HOST_KEY="" HARBOR_DAYTONA_WORKSPACE="./services/daytona/data" + +# ---------------------------------------------------------------- +# qq-ai-bot +# https://github.com/happysnaker/qq-ai-bot +# ---------------------------------------------------------------- +HARBOR_QQ_AI_BOT_HOST_PORT=35020 +HARBOR_QQ_AI_BOT_REVERSE_WS_HOST_PORT=35021 +HARBOR_QQ_AI_BOT_IMAGE="ghcr.io/happysnaker/qq-ai-bot" +HARBOR_QQ_AI_BOT_VERSION="latest" +HARBOR_QQ_AI_BOT_WORKSPACE="./services/qq-ai-bot" +HARBOR_QQ_AI_BOT_OPEN_URL="http://localhost:35020/readyz" +HARBOR_QQ_AI_BOT_SESSION_STORE="file" +HARBOR_QQ_AI_BOT_SESSION_TTL_MINUTES=120 +HARBOR_QQ_AI_BOT_REDIS_URL="" +HARBOR_QQ_AI_BOT_REDIS_KEY_PREFIX="qq-ai-bot" +HARBOR_QQ_AI_BOT_ONEBOT_ACCESS_TOKEN="" +HARBOR_QQ_AI_BOT_ALLOW_GROUP=true +HARBOR_QQ_AI_BOT_REQUIRE_MENTION_IN_GROUP=true +HARBOR_QQ_AI_BOT_ALLOW_PRIVATE=true +HARBOR_QQ_AI_BOT_ALLOW_GROUP_COMMANDS_WITHOUT_MENTION=false +HARBOR_QQ_AI_BOT_COMMAND_PREFIX="/" +HARBOR_QQ_AI_BOT_PROGRESS_MODE="message" +HARBOR_QQ_AI_BOT_OUTBOUND_MAX_TEXT_LENGTH=1400 +HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_WINDOW_MS=120000 +HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_MAX_ENTRIES=2048 +HARBOR_QQ_AI_BOT_ACP_REUSE_SESSION=true +HARBOR_QQ_AI_BOT_DEFAULT_SYSTEM_PROMPT="" +HARBOR_QQ_AI_BOT_ACP_VERBOSE_MODE="verbose" +HARBOR_QQ_AI_BOT_ACP_PERMISSION_STRATEGY="allow_once" +HARBOR_QQ_AI_BOT_ACP_PROGRESS_THROTTLE_MS=800 +HARBOR_QQ_AI_BOT_ACP_MAX_PROGRESS_UPDATES=6 +HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGES=3 +HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGE_BYTES=6291456 diff --git a/services/compose.qq-ai-bot.yml b/services/compose.qq-ai-bot.yml new file mode 100644 index 000000000..efe4468e5 --- /dev/null +++ b/services/compose.qq-ai-bot.yml @@ -0,0 +1,61 @@ +services: + qq-ai-bot: + container_name: ${HARBOR_CONTAINER_PREFIX}.qq-ai-bot + image: ${HARBOR_QQ_AI_BOT_IMAGE}:${HARBOR_QQ_AI_BOT_VERSION} + env_file: + - ./.env + - ./services/qq-ai-bot/override.env + environment: + BOT_HOST: 0.0.0.0 + BOT_PORT: 8080 + DATA_DIR: /app/data + SESSION_STORE: ${HARBOR_QQ_AI_BOT_SESSION_STORE} + SESSION_FILE_PATH: /app/data/sessions.json + SESSION_TTL_MINUTES: ${HARBOR_QQ_AI_BOT_SESSION_TTL_MINUTES} + REDIS_URL: ${HARBOR_QQ_AI_BOT_REDIS_URL} + REDIS_KEY_PREFIX: ${HARBOR_QQ_AI_BOT_REDIS_KEY_PREFIX} + APP_BUILD_REF: harbor + ONEBOT_MODE: reverse + ONEBOT_ACCESS_TOKEN: ${HARBOR_QQ_AI_BOT_ONEBOT_ACCESS_TOKEN} + ONEBOT_REVERSE_WS_HOST: 0.0.0.0 + ONEBOT_REVERSE_WS_PORT: 16700 + ONEBOT_REVERSE_WS_PATH: /onebot/v11/ws + ONEBOT_ALLOW_GROUP: ${HARBOR_QQ_AI_BOT_ALLOW_GROUP} + ONEBOT_REQUIRE_MENTION_IN_GROUP: ${HARBOR_QQ_AI_BOT_REQUIRE_MENTION_IN_GROUP} + ONEBOT_ALLOW_PRIVATE: ${HARBOR_QQ_AI_BOT_ALLOW_PRIVATE} + ONEBOT_ALLOW_GROUP_COMMANDS_WITHOUT_MENTION: ${HARBOR_QQ_AI_BOT_ALLOW_GROUP_COMMANDS_WITHOUT_MENTION} + ONEBOT_COMMAND_PREFIX: ${HARBOR_QQ_AI_BOT_COMMAND_PREFIX} + ONEBOT_GROUP_CONFIG_FILE: /app/config/group-rules.json + ONEBOT_PROGRESS_MODE: ${HARBOR_QQ_AI_BOT_PROGRESS_MODE} + ONEBOT_OUTBOUND_MAX_TEXT_LENGTH: ${HARBOR_QQ_AI_BOT_OUTBOUND_MAX_TEXT_LENGTH} + ONEBOT_INBOUND_DEDUPE_WINDOW_MS: ${HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_WINDOW_MS} + ONEBOT_INBOUND_DEDUPE_MAX_ENTRIES: ${HARBOR_QQ_AI_BOT_INBOUND_DEDUPE_MAX_ENTRIES} + ACP_CLIENT_NAME: qq-ai-bot + ACP_REUSE_SESSION: ${HARBOR_QQ_AI_BOT_ACP_REUSE_SESSION} + ACP_DEFAULT_SYSTEM_PROMPT: ${HARBOR_QQ_AI_BOT_DEFAULT_SYSTEM_PROMPT} + ACP_VERBOSE_MODE: ${HARBOR_QQ_AI_BOT_ACP_VERBOSE_MODE} + ACP_PERMISSION_STRATEGY: ${HARBOR_QQ_AI_BOT_ACP_PERMISSION_STRATEGY} + ACP_PROGRESS_THROTTLE_MS: ${HARBOR_QQ_AI_BOT_ACP_PROGRESS_THROTTLE_MS} + ACP_MAX_PROGRESS_UPDATES: ${HARBOR_QQ_AI_BOT_ACP_MAX_PROGRESS_UPDATES} + ACP_MAX_INBOUND_IMAGES: ${HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGES} + ACP_MAX_INBOUND_IMAGE_BYTES: ${HARBOR_QQ_AI_BOT_ACP_MAX_INBOUND_IMAGE_BYTES} + ports: + - ${HARBOR_QQ_AI_BOT_HOST_PORT}:8080 + - ${HARBOR_QQ_AI_BOT_REVERSE_WS_HOST_PORT}:16700 + volumes: + - ${HARBOR_QQ_AI_BOT_WORKSPACE}/data:/app/data + - ./services/qq-ai-bot/group-rules.json:/app/config/group-rules.json:ro + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:8080/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 20s + networks: + - harbor-network diff --git a/services/qq-ai-bot/group-rules.json b/services/qq-ai-bot/group-rules.json new file mode 100644 index 000000000..e85589c5e --- /dev/null +++ b/services/qq-ai-bot/group-rules.json @@ -0,0 +1,17 @@ +{ + "defaultSystemPrompt": "You are a stable, practical QQ AI assistant. Give the conclusion first, then the reason, risk, and next action. If you are unsure, say so plainly.", + "groups": { + "123456789": { + "name": "Tech discussion group", + "enabled": true, + "requireMention": true, + "systemPrompt": "You are serving a technical discussion group. Prefer conclusions, causes, risks, and concrete next steps." + }, + "223344556": { + "name": "Ops on-call group", + "enabled": true, + "requireMention": false, + "systemPrompt": "You are serving an operations on-call group. Keep replies short and execution-oriented." + } + } +} diff --git a/services/qq-ai-bot/override.env b/services/qq-ai-bot/override.env new file mode 100644 index 000000000..35135740a --- /dev/null +++ b/services/qq-ai-bot/override.env @@ -0,0 +1,6 @@ +# Service-native overrides for qq-ai-bot. +# Use `harbor env qq-ai-bot KEY value` or edit this file directly. +# Replace the mock ACP runtime with your own agent by editing these values. +ACP_AGENT_COMMAND=node +ACP_AGENT_ARGS_JSON=["dist/examples/mock-acp-agent.js"] +ACP_AGENT_WORKDIR=/app