Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions docs/develop/zzz/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,49 @@
| 收敛层 | `ZzzBackendContext`:持有 ctx、管生命周期、感知 / 操作方法 |
| 适配器 | MCP(`@mcp.tool`)+ HTTP(`/game/*`),并行、共享 backend |

当前已实现:**4 个感知 / 操作方法**(窗口状态 / 截图 / OCR / 进游戏)+ MCP / HTTP 适配器 + 服务入口 + 远程 SSH daemon。其余(run-as-service、事件桥、多实例、GUI 收敛)见各文档的「路线图」。
```mermaid
flowchart LR
GUI["GUI 开发工具<br/>MCP 服务页"] -->|启动/停止/探测| Entry["entry/server.py"]
CLI["uv run ... server"] --> Entry
Entry --> Backend["ZzzBackendContext"]
Backend --> ZContext["ZContext"]
Backend --> BasicSlot["RunSlot<br/>operation 运行"]
Backend --> AppSlot["ApplicationRunSlot<br/>一条龙/独立应用"]
Backend --> Schemas["schemas.py"]
Backend --> MCP["mcp/app.py<br/>mcp/service_app.py"]
Backend --> HTTP["http/routes.py<br/>http/service_routes.py"]
MCP --> Client["MCP 客户端"]
HTTP --> Tooling["HTTP 客户端/skill/脚本"]
```

当前已实现:

- 游戏感知与操作:窗口状态、截图、画面分析、进游戏、关闭游戏。
- 应用运行:列出应用、一条龙运行、独立应用运行。
- 统一状态:`query_status` / `stop` 同时覆盖 operation 与 application 运行。
- 对外协议:MCP streamable-http、MCP prompts 与 HTTP `/game/*` / `/health`。
- GUI 辅助:开发工具中的「MCP 服务」页可探测、启动、停止、重启本机 server,显示当前运行状态、滚动展示 server 日志并复制 MCP 地址。

## 怎么跑

命令行启动:

```shell
uv run --env-file .env python -m zzz_od.backend.entry.server --port 23001
```

启动后同时服务 `http://127.0.0.1:23001/mcp`(MCP)与 `/game/*`(HTTP)。详见 [entry.md](entry.md)。
如果项目根目录没有 `.env`,可省略 `--env-file .env`:

```shell
uv run python -m zzz_od.backend.entry.server --port 23001
```

GUI 启动:打开「开发工具 -> MCP 服务」,使用同一条入口命令在本机启动 `zzz_od.backend.entry.server` 子进程。

启动后:

- MCP:`http://127.0.0.1:23001/mcp`
- HTTP 探测:`http://127.0.0.1:23001/health`

## 文档索引(总—分)

Expand All @@ -39,6 +73,6 @@ uv run --env-file .env python -m zzz_od.backend.entry.server --port 23001

## 相关文档

- [一条龙整体架构](../../one_dragon/one_dragon_architecture.md) Layer 0 运行层
- [AI 编码助手接入](../../setup/ai_coding.md) MCP / skill 接入
- [AI Coding Harness 工程](../../harness/README.md) 方向 B 路线图
- [一条龙整体架构](../../one_dragon/one_dragon_architecture.md) - Layer 0 运行层
- [AI 编码助手接入](../../setup/ai_coding.md) - MCP / skill 接入
- [AI Coding Harness 工程](../../harness/README.md) - 方向 B 路线图
146 changes: 67 additions & 79 deletions docs/develop/zzz/backend/architecture.md
Original file line number Diff line number Diff line change
@@ -1,112 +1,100 @@
# 后端服务层架构

> `ZzzBackendContext` —— 绝区零一条龙的运行层(`ZContext`)之上的一层**传输无关**后端,把游戏感知 / 操作能力对外暴露给 MCP 与 HTTP 适配器。本文描述**当前已实现**的能力(3 个感知方法 + 运行态三件套 + `close_game`);未实现的扩展见 [§路线图](#路线图尚未实现)。MCP / HTTP 适配器见 [mcp.md](mcp.md) / [http.md](http.md),进程入口见 [entry.md](entry.md),MCP tool 设计规范见 [design-principles.md](design-principles.md)。

## 概述

后端服务层是一个 **headless 服务进程**:自己持有 `ZContext`(截图 / OCR / YOLO / 控制器 / 执行引擎),通过 `ZzzBackendContext` 收敛成一组**传输无关**方法,再由 MCP、HTTP 两个适配器并行对外暴露。GUI 是另一个独立入口,不经过本层。

三层:

- **Layer 0** —— `ZContext`(成熟运行核心,不改动)。
- **收敛层** —— `ZzzBackendContext`:持有 `ZContext`,管生命周期,暴露感知 / 操作方法。
- **适配器** —— MCP(`@mcp.tool`,原生 tool-call)+ HTTP(`/game/*`,通用 REST);两者共享同一 backend,各自序列化。
> `ZzzBackendContext` 是 `ZContext` 之上的传输无关 backend。它不关心调用方来自 MCP、HTTP 还是 GUI 管理页,只提供稳定的业务方法和运行状态。

## 概览

```mermaid
flowchart TB
Entry["entry/server.py"] --> Backend["backend_context.py<br/>ZzzBackendContext"]
Backend --> ZContext["ZContext<br/>截图/OCR/控制器/Application"]
Backend --> BasicSlot["RunSlot<br/>OpenAndEnterGame 等 Operation"]
Backend --> AppSlot["ApplicationRunSlot<br/>一条龙/独立应用"]
Backend --> Schemas["schemas.py<br/>传输无关 dataclass"]
Backend --> MCPBase["mcp/app.py<br/>基础 MCP tools"]
Backend --> MCPService["mcp/service_app.py<br/>应用运行 tools"]
MCPBase --> MCPPrompts["mcp/prompts.py<br/>MCP prompts"]
Backend --> HTTPBase["http/routes.py<br/>基础 HTTP routes"]
Backend --> HTTPService["http/service_routes.py<br/>应用运行 routes + /health"]
```

## 模块布局

```
```text
src/zzz_od/backend/
__init__.py
schemas.py # 传输无关返回结构:WindowStatus / OcrText / AnalyzeScreenResult / RunStatusResult
backend_context.py # ZzzBackendContext + BackendNotReadyError + RunState + RunSlot
schemas.py # WindowStatus / AnalyzeScreenResult / RunStatusResult / ApplicationListResult
backend_context.py # ZzzBackendContext + RunSlot + ApplicationRunSlot
mcp/
__init__.py
app.py # create_mcp_server + 7 个 @mcp.tool + _save_screenshot
app.py # create_mcp_server + 基础 game tools
service_app.py # list_applications / run_one_dragon / run_standalone_app
prompts.py # MCP prompt 案例与注册
http/
__init__.py
routes.py # register_http_routes + 7 个 /game/* 处理器
routes.py # register_http_routes + 基础 /game/* handler
service_routes.py # /health + 应用运行 HTTP handler
entry/
__init__.py
server.py # create_app + _serve + main(uvicorn,默认 23001)
server.py # create_app / uvicorn 入口
```

## ZzzBackendContext

`ZzzBackendContext` 在构造时持有(不继承)一个 `ZContext`,由服务入口注入(不用全局单例)
`ZzzBackendContext` 持有一个 `ZContext`,由服务入口注入。所有对外方法在进入业务逻辑前先检查 `ctx.ready_for_application`

### 生命周期
| 方法 | 作用 | 返回 |
|---|---|---|
| `check_window()` | 查询游戏窗口状态 | `WindowStatus` |
| `capture()` | 截取当前游戏画面 | RGB `MatLike` |
| `analyze()` | 截图 + OCR + 画面匹配 | `AnalyzeScreenResult` |
| `start_run(source, op_factory)` | 启动基础 operation | `(ok, future)` |
| `run_one_dragon(source)` | 按当前配置启动完整一条龙 | `(ok, future)` |
| `run_standalone_app(source, app_id=None)` | 启动独立应用 | `(ok, future)` |
| `list_applications()` | 列出当前实例可运行应用和独立应用选择状态 | `ApplicationListResult` |
| `query_status()` | 查询当前或最近一次运行状态 | `RunStatusResult` |
| `stop()` | 发出停止信号 | `dict` |
| `close_game()` | 发关闭窗口信号,不走运行槽 | `str` |

服务启动 / 关闭在线程池执行(`asyncio.to_thread`),不阻塞事件循环。
## 运行槽

> `ctx.init_async()` 返回 `None`(fire-and-forget),**不可 await**;要等初始化完成须 `asyncio.to_thread(ctx.init)`。
`RunSlot` 是 operation 运行槽,服务 `open_game` 这类 `Operation`。它负责:

所有方法前置校验 `ctx.ready_for_application`,未就绪抛 `BackendNotReadyError`。
- 单跑道并发拒绝。
- 后台线程执行 `op_factory(ctx).execute()`。
- 固化终态、最近状态、失败节点、耗时。
- 通过 `run_context.stop_running()` 发停止信号。

### 感知 / 操作方法
`ApplicationRunSlot` 继承 `RunSlot`,只保留应用运行差异:

| 方法 | 作用 | Layer 0 调用 | 返回 |
|---|---|---|---|
| `check_window()` | 游戏窗口状态 | `ctx.controller.game_win`(title / valid / active / scale / rect) | `WindowStatus` |
| `capture()` | 截图 | `controller.is_game_window_ready` + `get_screenshot(independent=False)` | RGB `MatLike` |
| `analyze()` | 截图 + OCR | `get_screenshot` + `ctx.ocr_service.get_ocr_result_list(image=)` | `AnalyzeScreenResult` |
| `start_run(source, op_factory)` | 派发长耗时 operation 到共享 `RunSlot` | 委托 `run_slot._start_run`:后台线程内 `run_context.start_running()` → `op_factory(ctx).execute()` → `finally stop_running()` 并固化终态 | `(ok, future)`:`ok=False` 表已有运行;`ok=True` 表已启动 |
| `query_status()` | 查询当前/最近一次运行状态 | 委托 `run_slot._query_status` | `RunStatusResult` |
| `stop()` | 发出停止信号 | 委托 `run_slot._stop` | `dict`(`{"stopped": bool, ...}`) |
| `close_game()` | 关闭游戏(秒级,**不走 RunSlot**) | `controller.close_game()`(`win.close`,吞异常不返) | `str`(已发送关闭信号;用 `check_window` 验证) |
- 委托 `run_context.run_application(app_id, instance_idx, group_id)`,复用 GUI 应用运行路径。
- 从 `run_context.last_application_result` 固化终态、最近状态和失败节点。
- 运行一条龙和独立应用,不塞进 operation 槽。

- backend 返回**原始数据**(图像 / 结构),持久化与协议格式交给适配器(MCP 落盘返路径、HTTP 直传字节)。
- 长耗时 operation 经 `start_run` 异步派发:适配器 `block=True` 时 `await asyncio.wrap_future(future)` 取结果,`block=False` 立刻返回、用 `query_status` 查进度。MCP / HTTP 对称暴露([design-principles.md](design-principles.md) P11)。
运行一条龙、独立应用和列应用前,`ZzzBackendContext` 会刷新当前进程内的实例配置,并清理 `ApplicationFactory` / 应用组缓存。这样 GUI 已写入 YAML 的设置更容易被外置 server 进程读取;仍不处理 GUI 主进程和 server 子进程同时操作游戏的跨进程互斥。

### RunSlot(单跑道运行槽)
`ZzzBackendContext.query_status()` 和 `ZzzBackendContext.stop()` 会同时检查两个槽。当前正在运行的槽优先;都不在运行时返回最近一次运行历史。

`RunSlot`(`backend_context.py`)是跨 MCP / HTTP 共享的运行态载体,由 `ZzzBackendContext` 持有一个实例(`backend.run_slot`)。设计要点:
## 适配器

- **单跑道**:单线程 `ThreadPoolExecutor(max_workers=1)`,`_start_run` 在锁内检查 `future` 未完成才派发,否则返回 `ok=False`(并发拒绝),保证独占资源不冲突。
- **固化终态**:状态判据用固化字段 `terminal_state`(`RunState` 枚举:`IDLE` / `RUNNING` / `SUCCESS` / `FAILED` / `STOPPED`),不读 `run_context` 推中间态;`_run` 在 `finally` 锁内固化 `terminal_state` / `last_status` / `failed_node` / `finished_at`,清空 `current_op`。
- **运行中读 operation**:运行期间 `_run` 在锁内把 `current_op` 暴露给 `_query_status`,可读当前节点 / 重试次数(终态后 `current_op` 销毁,仅留固化字段)。
- **跨适配器共享**:MCP(`source='mcp'`)与 HTTP(`source='http'`)调同一 `RunSlot`,HTTP 触发的运行 MCP 也能 `query_status` 查到。
- **停止语义**:`_stop` 只发停止信号,operation 在当前节点完成后退出(`OperationResult.status == '人工结束'` → `RunState.STOPPED`),非强杀;过渡期 `query_status` 仍报 `RUNNING`。
MCP 与 HTTP 只做传输适配:

### 返回结构(`schemas.py`)

三个 dataclass(具体定义见源码):

- **`WindowStatus`**:`win_title` / `is_win_valid` / `is_win_active` / `is_win_scale`,可选 `x` / `y` / `width` / `height`。
- **`OcrText`**:`text` / `x` / `y` / `width` / `height`。
- **`AnalyzeScreenResult`**:`success` / `ocr_texts: list[OcrText]` / `error: str | None`。

## 有状态资源约束

backend 独占持有 `ZContext`,适配器 / 前端不可并发直调其内部:

| 资源 | 约束 |
|---|---|
| `gpu_executor` | 单线程,强制串行 DirectML onnx session(YOLO 等) |
| 游戏窗口句柄 | `ZPcController → PcGameWindow`,win32 句柄,1080p,独占 |
| 输入注入 | `pyautogui` / `pydirectinput` 需管理员权限 + 交互式桌面会话 |
| OCR / YOLO session | onnxruntime InferenceSession,重资源 |
- MCP 基础工具在 `mcp/app.py`,应用运行工具在 `mcp/service_app.py`,prompt 在 `mcp/prompts.py`。
- HTTP 基础端点在 `http/routes.py`,应用运行和 `/health` 在 `http/service_routes.py`。
- 两边都只调用 `ZzzBackendContext` 公开方法,不直接操作运行槽私有状态。

## 进程模型

- 后端是**独立的 headless 服务入口**(`entry/server.py`),自己 `ZContext()` 再包 `ZzzBackendContext`;GUI 是另一个入口,二者择一运行(同 onedragon headless 模式)
- 每进程独占一个 `ZContext` → `gpu_executor` / 窗口句柄天然不冲突
- 服务进程常驻 `ZContext`,规避冷启动(OCR / YOLO 装载数秒)
- `entry/server.py` 是 headless server 入口,会创建独立 `ZContext`
- GUI 主程序仍是另一个入口;「开发工具 -> MCP 服务」页面启动的是本机 server 子进程
- 当前只保证同一 backend 进程内的运行互斥;GUI 主进程与外置 server 子进程之间不做跨进程互斥

## 路线图(尚未实现)

当前已实现:上述 3 个感知方法 + 运行态三件套(`start_run` / `query_status` / `stop`,经共享 `RunSlot`)+ `close_game`(独立同步关游戏)+ MCP / HTTP 适配器 + 入口 + 远程 SSH daemon(见 [remote-ssh.md](remote-ssh.md))。后续扩展:

- **run-as-service**:`run_application` / `pause` —— 把一条龙 Application 当可控服务跑。`status` / `stop` 已实现(本次,经 `RunSlot` 暴露的运行态三件套)。
- **事件桥**:`subscribe_events` —— 把日志 / 运行状态 / overlay-debug 事件桥成可订阅流(WS 推 web、MCP notifications 推 AI)。
- **多实例**:`list_instances` / `switch_instance` —— 账号实例切换。
- **更多 game 能力**:`identify_current_screen`(屏幕识别)、`click_at_position`(按坐标点击)。
- **GUI 收敛**:将来 GUI 入口改走 backend(接口形状已对齐:`run_application` / `pause` / `subscribe_events`)。
- 事件推送:WebSocket / SSE 或 MCP notifications。
- 多实例:`list_instances` / `switch_instance`。
- 更多 game 感知与交互 tool。
- 更完整的 AI 操作范式。

## 相关文档

- [README.md](README.md) — 总览
- [mcp.md](mcp.md) — MCP 适配器
- [http.md](http.md) — HTTP 适配器
- [design-principles.md](design-principles.md) — MCP tool 设计规范(P1–P12)
- [entry.md](entry.md) — 服务入口
- [一条龙整体架构](../../one_dragon/one_dragon_architecture.md) — Layer 0 运行层
- [README.md](README.md) - 总览
- [mcp.md](mcp.md) - MCP 适配器
- [http.md](http.md) - HTTP 适配器
- [entry.md](entry.md) - 服务入口
58 changes: 47 additions & 11 deletions docs/develop/zzz/backend/entry.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,68 @@

> `ZzzBackendContext` 的进程入口:装配 backend + MCP / HTTP 适配器,uvicorn 运行。本地 headless 入口见下;远程 SSH daemon(管本入口启停)见 [remote-ssh.md](remote-ssh.md)。

## 运行
## 命令行运行

```shell
uv run --env-file .env python -m zzz_od.backend.entry.server --host 127.0.0.1 --port 23001
```

启动流程:`ZContext()` → `ZzzBackendContext` → `await backend.start()`(线程池初始化,不阻塞事件循环)→ 装配 MCP(`/mcp`)+ HTTP(`/game/*`)到同一 app → `uvicorn.serve` → 关闭时 `backend.shutdown()`。
如果项目根目录没有 `.env`,可省略 `--env-file .env`:

CLI:`--host`(默认 `127.0.0.1`)、`--port`(默认 **23001**)。
```shell
uv run python -m zzz_od.backend.entry.server --host 127.0.0.1 --port 23001
```

启动流程:

1. 创建 `ZContext()`。
2. 创建 `ZzzBackendContext(ctx)`。
3. `await backend.start()` 在线程池中执行同步初始化。
4. 注册 MCP `/mcp` 和 HTTP `/health`、`/game/*`。
5. `uvicorn.serve` 监听本机端口。
6. 关闭时调用 `backend.shutdown()`。

CLI 参数:

- `--host`:默认 `127.0.0.1`。
- `--port`:默认 `23001`。

## GUI 启动

GUI 的「开发工具 -> MCP 服务」页面提供本机 server 管理:

- 探测:请求 `http://127.0.0.1:<port>/health`。
- 启动:在项目根目录执行 `uv run python -m zzz_od.backend.entry.server --port <port>`;如果项目根目录存在 `.env`,会自动补上 `--env-file .env`。
- 停止 / 重启:查找并管理 `zzz_od.backend.entry.server` 进程。
- 日志:`.debug/zzz_od_mcp/main_server.log`,默认关闭 uvicorn access log,避免状态轮询刷屏。
- MCP 地址:`http://127.0.0.1:<port>/mcp`。
- 当前运行状态:请求 `http://127.0.0.1:<port>/game/status`。

页面会低频轮询 `/game/status`,并尾读 `.debug/zzz_od_mcp/main_server.log` 到消息框;这些日志只用于 GUI 展示,不通过 MCP tool 返回给 agent。GUI 会过滤自身轮询、`GET /mcp` 探测和 Windows 连接重置这类噪音,并限制消息框保留行数,避免长时间打开后卡顿。

这个 GUI 页面管理的是一个本机 server 子进程,不是把 MCP server 嵌进 GUI 主进程。当前不做 GUI 主进程与 server 子进程之间的跨进程运行互斥。

## `.env`

`uv run --env-file .env ...` 会要求项目根目录存在 `.env`。如果本地没有 `.env`,命令会在启动前报错。GUI 启动会先判断 `.env` 是否存在;命令行手动启动时,开发环境可以按项目需要创建 `.env`,或在不需要环境变量的场景下省略 `--env-file .env`。

## 进程模型

- 独立 headless 入口,自己持有 `ZContext`;GUI 是另一个入口,二者择一(同 onedragon headless 模式)
- 每进程独占一个 `ZContext` → `gpu_executor` / 窗口句柄天然不冲突
- 常驻 `ZContext`,规避冷启动(OCR / YOLO 装载数秒)
- server 进程独立持有一个 `ZContext`。
- 每个进程内通过运行槽保证同进程单跑道
- 常驻 `ZContext` 可避免 OCR / YOLO 冷启动成本

## 依赖(dev 组)

- `mcp`(FastMCP / streamable-http)、`uvicorn`(ASGI server)。
- `mcp`:FastMCP / streamable-http。
- `uvicorn`:ASGI server。

## 远程 SSH

远程 SSH 场景经 daemon 管 server 启停(daemon 已实现,详见 [remote-ssh.md](remote-ssh.md))。**RDP / SSH 限制**:输入注入需管理员 + 交互式桌面会话;远程场景靠 daemon 绕开 Windows 会话隔离
远程 SSH 场景由 daemon 管 server 启停详见 [remote-ssh.md](remote-ssh.md)。

## 相关文档

- [architecture.md](architecture.md) backend 生命周期
- [mcp.md](mcp.md) — 主服务器 tool
- [http.md](http.md) HTTP 端点
- [architecture.md](architecture.md) - backend 生命周期和进程模型
- [mcp.md](mcp.md) - MCP tool
- [http.md](http.md) - HTTP 端点
Loading
Loading