# 多应用桥接框架设计

> 目标：将 bridge 从"微信专属 API 服务"演进为"容器内多桌面应用的统一桥接框架"。一个容器内可同时运行多个应用（微信 / 小红书 / Telegram / Chromium / 自定义），每个应用作为独立实例被 bridge 管理，对外暴露结构化接口。

---

> **阅读提示**：本文档是**目标架构设计**，同时用 `[现状]` 标注当前代码的实际状态。当前 bridge 仍是一个约 2400 行的单文件服务（[bridge/server.py](file:///D:/WechatOnCloud-main/bridge/server.py)），全局单例、微信强耦合；docker 与 panel 层已支持"一容器一应用"的多应用类型，但尚未支持"单容器多应用"。因此本文先聚焦 bridge 层改造，使其在"多容器单应用"模式下即可工作，单容器多应用作为后续扩展。

---

## 1. 背景与转变

### 1.1 现状

bridge 当前是**单应用、单进程、强耦合微信**的 API 服务：

- [bridge/server.py](../bridge/server.py) 约 2400 行单文件，全局单例 `_state = AppState()` 持有唯一的 `XdotoolDriver`、`DbReader`、`SendQueue`、`QrCapture`。
- 所有路由均为 `/api/*` 无前缀（如 `/api/send/text`、`/api/messages/since`），直接操作全局 `_state`，没有 `app_id` 概念。
- 响应模型字段名微信中心化：`wechat_running`、`wechat_window_found`、错误码 `WECHAT_NOT_RUNNING` / `WECHAT_NOT_LOGGED_IN`。
- DB 解密、联系人/消息读取、二维码登录全部写死微信表结构与 SQLCipher 参数。
- 一容器只跑一个应用（由 [docker/app-defs.sh](../docker/app-defs.sh) 的 `WOC_APP_TYPE` 决定）；想新增小红书/Telegram 自动化，当前只能再起一个容器。

### 1.2 转变

用户需求：**一个容器内同时跑多个应用，每个应用对外暴露接口**。

这要求 bridge 从"微信适配器"升格为"应用桥接框架"：

| 维度 | 现状 | 目标 |
|------|------|------|
| 容器内应用数 | 1（由 `WOC_APP_TYPE` 决定） | N（动态注册） |
| bridge 进程 | 跟随单一应用 | 一个 bridge 管理多个应用实例 |
| 路由 | 微信专属路由无前缀 | 通用路由无前缀 + 每个应用实例独立命名空间 |
| 状态 | 全局单例 `_state` | 按 app_id 隔离的 AppInstance 容器 |
| 资源调度 | 不需要 | 必须串行化 X11 / 剪贴板 / 焦点操作 |

### 1.3 已具备的基础（非本阶段改造重点）

- docker 层已通过 `WOC_APP_TYPE` 支持多种应用类型：`wechat` / `telegram` / `chromium` / `custom`（[docker/app-defs.sh](../docker/app-defs.sh)）。
- panel 的 `Instance.appType` 已能标识实例承载的应用类型，并透传给容器环境变量（[panel/server/src/store.ts](../panel/server/src/store.ts)）。
- bridge 基于 FastAPI + Pydantic，已有统一的 `BridgeError` 异常模型与全局异常处理器，为路由拆分和错误码扩展提供了基础。

> **本文档范围**：仅聚焦 bridge 层改造；panel UI、docker 单容器多应用启动机制不在本阶段文档范围内。

---

## 2. 核心理念

### 2.1 三层抽象

```
应用类型 (AppKind)        — 静态描述：微信 / 小红书 / Telegram / Chromium / Custom
   ↓ 实例化
应用实例 (AppInstance)    — 运行期实体：唯一 app_id、独立状态、独立窗口
   ↓ 适配
应用驱动 (AppDriver)      — 该实例的能力实现：发消息 / 发笔记 / 读 DB / 截图
```

- **AppKind** 是注册表项，定义"这类应用能做什么、怎么启动、需要什么依赖"
- **AppInstance** 是运行期实体，一个容器内可以同时存在多个同类实例（如两个微信账号、两个小红书账号）
- **AppDriver** 是实例的能力实现层，框架通过它操作具体应用

### 2.2 框架职责边界

**框架负责（应用无关）**：

- HTTP 服务、路由分发、鉴权、限流
- 请求/响应日志、异常处理、错误码体系
- X11 串行化调度（避免多应用并发争用同一个 X server）
- 截图、剪贴板、窗口管理（通用 X11 能力）
- 应用生命周期管理（启动 / 停止 / 重启 / 健康检查）
- 诊断框架（通用检查项 + 应用自报检查项）
- 配置管理、命令行参数、环境变量

**应用驱动负责（应用相关）**：

- 进程启动命令与参数
- 窗口识别（按 title / class / PID）
- 登录态检测与登录流程
- 应用专属能力实现（发消息、发笔记、读 DB、搜索等）
- 应用专属路由注册
- 应用专属诊断项

> **[现状]** 当前所有职责都混在 [bridge/server.py](../bridge/server.py) 中，没有框架/驱动分层；`XdotoolDriver` 直接实现微信专用方法，`SendQueue` 是全局单例限流器，UIScheduler 尚未引入。

### 2.3 设计原则

1. **组合优于继承**：driver 持有通用工具实例（`self.xd`），不继承工具类
2. **一应用一命名空间**：每个实例独立 `app_id`，路由与状态都按 `app_id` 隔离
3. **声明式能力**：driver 声明能力集合，框架据此挂载路由与协商
4. **串行化 X11**：所有窗口操作经统一 UIScheduler 排队执行
5. **应用无关的 DB 抽象延迟**：SQLCipher 解密暂留微信 driver 内，待第二个 SQLCipher 应用出现再抽公共层
6. **向后兼容过渡**：旧路径与新路径并存，alias 机制保证现有调用方不破坏

---

## 3. 总体架构

```
┌─────────────────────────────────────────────────────────────────┐
│ Docker 容器                                                       │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ bridge 框架进程 (FastAPI, :8088)                          │   │
│  │                                                            │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │   │
│  │  │ 通用路由层    │  │ UIScheduler  │  │ AppManager   │    │   │
│  │  │ /api/status   │  │ (X11 串行)   │  │ (生命周期)   │    │   │
│  │  │ /api/apps     │  │              │  │              │    │   │
│  │  │ /api/screens  │  └──────────────┘  └──────┬───────┘    │   │
│  │  │ /api/diag/*   │                              │            │   │
│  │  └──────────────┘                              │            │   │
│  │                                  ┌─────────────┼─────────┐  │   │
│  │                                  │             │         │  │   │
│  │              ┌───────────────────┴───┐  ┌──────┴───────┐  │   │
│  │              │ AppInstance[wx_a1]    │  │ AppInstance   │  │   │
│  │              │  └ WechatDriver       │  │   [xhs_b2]    │  │   │
│  │              │    routes: /api/apps/  │  │  └ XhsDriver  │  │   │
│  │              │      wx_a1/*           │  │    routes:    │  │   │
│  │              │    capabilities:       │  │    /api/apps/ │  │   │
│  │              │      text_send,        │  │      xhs_b2/* │  │   │
│  │              │      db_read,          │  │    capabilities│  │   │
│  │              │      login_qr          │  │      publish, │  │   │
│  │              └───────────────────────┘  │      search   │  │   │
│  │                                        └──────────────┘  │   │
│  └────────────────────────────────────────────────────────┘   │
│                                                                   │
│  X server (Xvfb :1)  ◀── 所有应用共用，UIScheduler 串行化调度     │
│     ├─ 微信窗口 (wxid_abc)                                        │
│     ├─ chromium 窗口 (xiaohongshu)                                │
│  KasmVNC (:3000/3001) ◀── web 桌面串流                            │
│                                                                   │
│  数据卷 /config/                                                   │
│     ├─ wechat/<wxid>/        微信数据                             │
│     ├─ xiaohongshu/          小红书 cookie + 数据                 │
│     ├─ telegram/             Telegram 数据                        │
│     └─ apps.json             应用实例注册表（持久化）              │
└─────────────────────────────────────────────────────────────────┘
          ▲
          │ HTTP Bearer Token
   ┌──────┴──────┐
   │  panel 面板  │  ◀── 统一入口，调度多容器、多应用
   └─────────────┘
```

> **[现状]** 上图是目标态。当前每个 Docker 容器内只运行**一个**应用实例，bridge 进程也按单实例设计；`AppManager`、`UIScheduler`、`apps.json` 均未实现。本阶段优先让 bridge 在"多容器单应用"模式下具备框架能力（每个容器一个 bridge、一个 driver、一个 AppInstance），为未来的"单容器多应用"保留扩展点。

---

## 4. 核心概念

### 4.1 AppKind（应用类型）

静态注册项，描述"这类应用是什么"。一个 AppKind 在 bridge 启动时注册到 `AppKindRegistry`。

| 属性 | 说明 | 示例 |
|------|------|------|
| `kind_id` | 唯一标识 | `"wechat"` / `"xiaohongshu"` / `"telegram"` / `"chromium"` / `"custom"` |
| `name` | 显示名 | `"微信"` / `"小红书"` |
| `driver_class` | AppDriver 实现类 | `WechatDriver` / `XiaohongshuDriver` |
| `default_capabilities` | 默认能力集合 | `{"text_send","db_read","login_qr"}` |
| `binary_path` | 可执行文件路径（用于检测是否已安装） | `/config/wechat/opt/wechat/wechat` |
| `launch_args_template` | 启动参数模板（支持变量替换） | `"--user-data-dir={data_dir}"` |
| `single_instance` | 是否限制单实例（如系统托盘类应用） | `true` / `false` |

> **[现状]** `AppKindRegistry` 尚未实现。当前应用类型由容器环境变量 `WOC_APP_TYPE` 决定，bridge 代码中无对应抽象；但目标类的字段（`kind_id`、`driver_class`、`binary_path` 等）可直接映射到现有 docker/app-defs.sh 的定义。

### 4.2 AppInstance（应用实例）

运行期实体，代表一个正在运行（或已注册待启动）的应用。每个实例有唯一 `app_id`。

| 属性 | 说明 | 示例 |
|------|------|------|
| `app_id` | 全局唯一实例标识 | `"wx_a1"` / `"xhs_b2"`（kind 前缀 + 短随机） |
| `kind_id` | 所属应用类型 | `"wechat"` |
| `display_name` | 用户可读名 | `"工作微信"` / `"小红书主号"` |
| `data_dir` | 该实例独立数据目录 | `/config/apps/wx_a1` |
| `window_id` | X11 窗口 ID（运行期动态） | `0x380000a` |
| `pid` | 进程 PID | `1234` |
| `login_state` | 当前登录态 | `"logged_in"` / `"need_login"` / `"not_running"` |
| `capabilities` | 实际可用能力（动态计算） | `{"text_send"}`（DB 未解锁时 db_read 不在集合内） |
| `status` | 实例状态 | `"registered"` / `"starting"` / `"running"` / `"stopped"` / `"crashed"` |

**实例寻址**：所有应用专属路由以 `/api/apps/{app_id}/` 为前缀，框架据此路由到对应实例的 driver。

> **[现状]** `AppInstance` 数据类尚未实现。当前 bridge 的运行期状态是全局单例 `_state`，没有 `app_id`。本阶段每个 bridge 进程仍只管理一个实例，可把该实例的 `app_id` 固定为 `"default"` 或从 `WOC_APP_TYPE` 派生，使新路由层能提前落地。

### 4.3 AppDriver（应用驱动）

每种 AppKind 对应一个 AppDriver 实现类，负责该类应用的所有操作。driver 实例与 AppInstance 一一绑定。

```python
class AppDriver(ABC):
    kind_id: ClassVar[str]
    default_capabilities: ClassVar[set[str]]

    def __init__(self, instance: AppInstance, ctx: BridgeContext) -> None:
        self.instance = instance
        self.ctx = ctx                    # 框架通用能力（xd / scheduler / screenshot）
        self.xd = ctx.xd_factory(instance)  # 该实例专属的 XdotoolBase

    # 生命周期
    async def install(self) -> None: ...      # 首次安装（下载/解压）
    async def start(self) -> None: ...        # 启动进程
    async def stop(self) -> None: ...         # 停止进程
    async def restart(self) -> None: ...
    async def is_running(self) -> bool: ...
    async def health_check(self) -> dict: ...

    # 窗口与登录
    async def find_window(self) -> int | None: ...
    async def activate_window(self) -> None: ...
    async def detect_login_state(self) -> str: ...
    async def wait_for_login(self, timeout: int) -> None: ...
    async def logout(self) -> None: ...

    # 能力查询
    def capabilities(self) -> set[str]: ...   # 返回当前实际可用能力（动态）

    # 路由注册
    def register_routes(self, router: APIRouter) -> None: ...

    # 诊断
    def diagnostic_items(self) -> list[dict]: ...
    async def diagnostic_run(self, check_id: str) -> dict: ...
    async def diagnostic_autofix(self, check_id: str) -> dict: ...
```

> **[现状]** `AppDriver` 抽象基类尚未实现。当前微信相关逻辑直接写在 [bridge/server.py](../bridge/server.py) 和 [bridge/xdotool_driver.py](../bridge/xdotool_driver.py) 中，二者均强耦合微信。拆分时建议：
> 1. 把 `xdotool_driver.py` 中通用 X11 操作（`_run`、`_key`、`_paste_via_xclip`、窗口几何）抽成 `XdotoolBase`；
> 2. 把微信专属方法（`find_wechat_window`、`send_text`、`_open_session_by_name`）迁到 `drivers/wechat/driver.py` 的 `WechatDriver`；
> 3. 微信 DB 读写、密钥提取、二维码登录迁到 `drivers/wechat/` 子模块。

### 4.4 BridgeContext（框架上下文）

driver 通过它访问框架通用能力。框架保证线程安全与串行化。

| 字段 | 类型 | 说明 |
|------|------|------|
| `config` | `BridgeConfig` | 全局配置 |
| `xd_factory` | `Callable[[AppInstance], XdotoolBase]` | 为每个实例创建 X11 工具（绑定到该实例窗口） |
| `ui_scheduler` | `UIScheduler` | X11 操作串行化调度器 |
| `screenshot` | `Screenshot` | 通用截图能力 |
| `send_queue` | `SendQueue` | UI 操作串行化队列（多应用共享） |
| `logger` | `logging.Logger` | 框架 logger |

---

## 5. 模块设计

### 5.1 目录结构

```
bridge/
  core/                              框架核心（应用无关）
    __init__.py
    context.py                       BridgeContext
    config.py                        BridgeConfig（命令行 + env）
    server.py                        FastAPI app 工厂 + lifespan + 全局中间件
    logging.py                       logger 配置 + 请求日志中间件
    errors.py                        BridgeError + ErrorResponse + 错误码表
    models.py                        通用响应模型（StatusResponse / AppInstanceInfo 等）
    xdotool_base.py                  X11 通用操作基类
    screenshot.py                    通用截图
    send_queue.py                    UI 串行化队列
    ui_scheduler.py                  X11 操作调度器（多应用串行）
    diagnostic.py                    通用诊断框架
    app_manager.py                   AppManager（实例生命周期管理）
    app_kind.py                      AppKind + AppKindRegistry（类型注册表）
    app_instance.py                  AppInstance 数据类
    lifecycle.py                     InitState 后台任务框架
  drivers/                           应用适配器
    __init__.py
    base.py                          AppDriver 抽象基类
    wechat/
      __init__.py
      driver.py                      WechatDriver(AppDriver)
      db_reader.py                   微信 DB 读取
      decryptor.py                   SQLCipher 解密
      key_cache.py                   密钥缓存
      key_extractor.py               内存扫描提 key
      models.py                      微信专属请求/响应模型
      routes.py                      微信专属路由
      diagnostic.py                  微信诊断项
    xiaohongshu/
      __init__.py
      driver.py                      XiaohongshuDriver(AppDriver)
      routes.py                      小红书专属路由
      models.py
    telegram/
      __init__.py
      driver.py                      TelegramDriver(AppDriver)
      routes.py
    chromium/
      __init__.py
      driver.py                      ChromiumDriver(AppDriver) 通用浏览器
    custom/
      __init__.py
      driver.py                      CustomDriver(AppDriver)
  tools/                             调试脚本
  s6/                                s6 服务定义
  main.py                            入口：解析参数 → 注册 kinds → 启动 app
```

> **[现状]** 上述 `core/`、`drivers/`、`main.py` 均为目标结构。当前 bridge 目录扁平：所有代码在 [bridge/server.py](../bridge/server.py)、[bridge/xdotool_driver.py](../bridge/xdotool_driver.py)、[bridge/send_queue.py](../bridge/send_queue.py) 等根级文件中。S1 阶段可按以下顺序迁移，避免一次性大爆炸：
> 1. 先把 `server.py` 中通用配置/错误/模型迁到 `core/`；
> 2. 创建 `drivers/base.py` 与 `drivers/wechat/`；
> 3. 保持 `main.py` 仍只注册一个 `WechatDriver`，行为与旧版完全一致。

### 5.2 AppManager（应用管理器）

框架核心组件，管理容器内所有 AppInstance 的生命周期。

**职责**：

- 维护实例注册表（内存 + 持久化到 `/config/apps.json`）
- 提供实例 CRUD：`register` / `unregister` / `list` / `get`
- 控制实例生命周期：`start` / `stop` / `restart`
- 健康检查：周期性轮询所有 running 实例的 `is_running`
- 自动恢复：crashed 实例按策略重启（最多 N 次/小时）

**实例注册表结构**（`/config/apps.json`）：

```json
{
  "version": 1,
  "instances": [
    {
      "app_id": "wx_a1",
      "kind_id": "wechat",
      "display_name": "工作微信",
      "data_dir": "/config/apps/wx_a1",
      "auto_start": true,
      "created_at": 1735900800
    },
    {
      "app_id": "xhs_b2",
      "kind_id": "xiaohongshu",
      "display_name": "小红书主号",
      "data_dir": "/config/apps/xhs_b2",
      "auto_start": false,
      "created_at": 1735900900
    }
  ]
}
```

> **[现状]** `AppManager` 与 `/config/apps.json` 均未实现。当前实例生命周期（容器启停、应用安装）由 panel 通过 Docker API 管理，bridge 只感知当前容器内已启动的单个应用。本阶段改造不推翻 panel 的生命周期管理，bridge 内只保留单实例的运行期抽象，为后续多实例注册表预留接口。

### 5.3 UIScheduler（X11 操作调度器）

**问题**：多应用共用一个 X server，并发操作会导致：

- 窗口焦点争用（A 激活窗口时 B 的操作打到 A）
- 剪贴板污染（A 写剪贴板，B 还没读就被覆盖）
- 键盘事件错位（A 的 Ctrl+V 发到 B 的窗口）

**解决**：所有 X11 操作（activate / key / paste / click / type）必须经 UIScheduler 排队执行。

```python
class UIScheduler:
    """X11 操作串行化调度器。

    所有 driver 的 X11 操作通过 submit() 提交，调度器按 FIFO 顺序
    串行执行。同一时间只有一个操作在 X server 上进行。
    """

    async def submit(self, coro_factory: CoroFactory, *, app_id: str) -> Any:
        """提交一个 X11 操作。

        Args:
            coro_factory: 返回 coroutine 的工厂（便于重试与超时控制）
            app_id: 发起方实例 ID（用于日志与死锁检测）

        Returns:
            操作结果

        Notes:
            - 操作间最小间隔 100ms（避免 X server 压力）
            - 单操作超时 30s（避免死锁）
            - 操作日志带 app_id 前缀，便于排障
        """
```

**driver 使用方式**：

```python
class WechatDriver(AppDriver):
    async def send_text(self, to: str, content: str) -> str:
        async def _op():
            await self.xd.activate_window(self.instance.window_id)
            await self.xd._open_session_by_name(to)
            await self.xd._paste_via_xclip(content)
            await self.xd._key("Return")
            return f"local_{int(time.time())}_{random.randint(0,0xFFFFFF):06x}"
        return await self.ctx.ui_scheduler.submit(_op, app_id=self.instance.app_id)
```

**不进 UIScheduler 的操作**：

- 读窗口列表（`xdotool search`，只读）
- 读进程状态（`pgrep`，只读）
- 读 DB（不涉及 X11）
- 截图（`scrot` 本身是只读快照，不争用焦点）

### 5.4 SendQueue 与 UIScheduler 的关系

| 组件 | 作用 | 粒度 |
|------|------|------|
| `SendQueue` | 发送类操作的限流（防止频率过高触发风控） | 单实例：每个 driver 自己一个队列 |
| `UIScheduler` | X11 操作的串行化（防止多应用争用 X server） | 全局：所有 driver 共享 |

两者正交：一个操作可能先经 SendQueue 限流，再经 UIScheduler 串行执行。

> **[现状]** 当前只有一个全局 [SendQueue](../bridge/send_queue.py)，用于单应用发送限流；`UIScheduler` 尚未实现，X11 操作在 HTTP 层并发调用时存在焦点/剪贴板冲突风险。S1 阶段可保持 `SendQueue` 不变，S2 引入多实例后再把 `SendQueue` 下放到每个 `AppInstance`，同时新增全局 `UIScheduler`。

---

## 6. 接口规范

### 6.1 路由分层

```
/api/*                       通用路由（无 app_id，框架级）
  /api/status                框架状态（聚合所有应用摘要）
  /api/apps                  应用实例列表 / 注册新实例
  /api/apps/{app_id}         单实例状态 / 控制
  /api/apps/{app_id}/start   启动实例
  /api/apps/{app_id}/stop    停止实例
  /api/apps/{app_id}/restart 重启实例
  /api/kinds                 支持的应用类型列表
  /api/screenshot            全屏截图（所有应用窗口）
  /api/diagnostic/items      通用诊断项 + 所有实例诊断项聚合
  /api/diagnostic/run/{id}   执行诊断
  /api/diagnostic/autofix/{id}

/api/apps/{app_id}/*         应用专属路由（由 driver.register_routes 挂载）
  /api/apps/{app_id}/messages        微信消息拉取
  /api/apps/{app_id}/contacts        微信联系人
  /api/apps/{app_id}/send/text       微信发文本
  /api/apps/{app_id}/login/qr        微信扫码登录
  /api/apps/{app_id}/db/decrypt      微信 DB 解密
  /api/apps/{app_id}/xhs/publish     小红书发笔记
  /api/apps/{app_id}/xhs/search      小红书搜索
```

> **[现状]** 当前所有路由都是 `/api/*` 无前缀（如 `/api/send/text`、`/api/messages/since`、`/api/db/decrypt`），且全部写死微信逻辑。本阶段目标：
> 1. 新增 `/api/apps/{app_id}/*` 路由层；
> 2. 将旧路由作为 alias 内部转发到 `/api/apps/default/*`；
> 3. `/api/status` 同时返回旧字段（取 default app）与新 `apps` 数组，保证面板零改动。

### 6.2 通用路由详述

#### GET /api/status

返回框架整体状态 + 所有实例摘要。

```json
{
  "bridge_version": "2.0.0",
  "uptime_seconds": 3600,
  "display": ":1",
  "app_count": 2,
  "apps": [
    {
      "app_id": "wx_a1",
      "kind_id": "wechat",
      "display_name": "工作微信",
      "status": "running",
      "login_state": "logged_in",
      "capabilities": ["text_send", "db_read", "login_qr"]
    },
    {
      "app_id": "xhs_b2",
      "kind_id": "xiaohongshu",
      "display_name": "小红书主号",
      "status": "running",
      "login_state": "need_login",
      "capabilities": ["publish", "search"]
    }
  ]
}
```

#### GET /api/apps

列出所有实例。

#### POST /api/apps

注册新实例。

```json
// 请求
{
  "kind_id": "xiaohongshu",
  "display_name": "小红书小号",
  "auto_start": true
}

// 响应
{
  "app_id": "xhs_c3",
  "kind_id": "xiaohongshu",
  "display_name": "小红书小号",
  "data_dir": "/config/apps/xhs_c3",
  "status": "registered"
}
```

#### GET /api/apps/{app_id}

单实例详细状态。

```json
{
  "app_id": "wx_a1",
  "kind_id": "wechat",
  "display_name": "工作微信",
  "status": "running",
  "pid": 1234,
  "window_id": "0x380000a",
  "login_state": "logged_in",
  "capabilities": ["text_send", "db_read", "login_qr", "media"],
  "uptime_seconds": 1800,
  "db_accessible": true,
  "current_wxid": "wxid_abc",
  "current_nickname": "张三"
}
```

#### POST /api/apps/{app_id}/start|stop|restart

控制实例生命周期。

#### GET /api/kinds

列出支持的 AppKind。

```json
{
  "kinds": [
    {
      "kind_id": "wechat",
      "name": "微信",
      "default_capabilities": ["text_send", "db_read", "login_qr", "media"],
      "single_instance": false,
      "installed": true
    },
    {
      "kind_id": "xiaohongshu",
      "name": "小红书",
      "default_capabilities": ["publish", "search"],
      "single_instance": false,
      "installed": true
    }
  ]
}
```

### 6.3 应用专属路由规范

由各 driver 通过 `register_routes(router)` 挂载，router 自动绑定 `/api/apps/{app_id}` 前缀。

**driver 实现示例**：

```python
# drivers/wechat/routes.py
def register_routes(router: APIRouter) -> None:
    @router.get("/messages/since")
    async def get_messages(app_id: str, cursor: int = 0, limit: int = 50):
        driver = app_manager.get_driver(app_id)
        return await driver.get_messages_since(cursor, limit)

    @router.post("/send/text")
    async def send_text(app_id: str, req: SendTextRequest):
        driver = app_manager.get_driver(app_id)
        return await driver.send_text(req.to, req.content)
```

### 6.4 错误码扩展

新增框架级错误码：

| 错误码 | HTTP | 说明 |
|--------|------|------|
| `APP_NOT_FOUND` | 404 | app_id 不存在 |
| `APP_NOT_RUNNING` | 503 | 实例未运行 |
| `APP_NOT_LOGGED_IN` | 401 | 实例未登录 |
| `APP_KIND_UNKNOWN` | 400 | 未知应用类型 |
| `APP_KIND_NOT_INSTALLED` | 503 | 应用类型未安装 |
| `APP_ALREADY_EXISTS` | 409 | 实例已存在（single_instance 冲突） |
| `UI_BUSY` | 503 | UIScheduler 队列拥塞 |
| `APP_CAPABILITY_NOT_SUPPORTED` | 501 | 该实例不支持请求的能力 |

> **[现状]** 当前 [bridge/models.py](../bridge/models.py) 中只有微信中心化错误码，如 `WECHAT_NOT_RUNNING`、`WECHAT_NOT_LOGGED_IN` 等。拆分阶段应：
> 1. 保留旧错误码作为微信 driver 的返回值；
> 2. 新增框架级错误码上表；
> 3. alias 路由命中时仍可使用旧错误码，避免面板/外部系统改动。

### 6.5 向后兼容层

现有无前缀路由（`/api/messages`、`/api/send/text` 等）保留为 alias，内部转发到 `/api/apps/{default_app_id}/...`。

**默认实例选择规则**：

1. 若容器内只有一个实例，自动作为 default
2. 若多实例，读取 `/config/.woc-default-app`（由面板或首次注册时写入）
3. 都没有则返回 404

**alias 路由行为**：

- 命中 alias 时日志打 `DEPRECATED` warning，提示调用方迁移
- alias 在 major 版本升级时下线（v3.0.0 移除）

### 6.6 与既有系统的兼容性约束

改造必须尊重以下已验证的硬约束，否则现有微信实例会损坏：

1. **鉴权**：`/api/bridge/:id/*` 外部调用依赖 `WOC_BRIDGE_API_TOKEN` 作为 Bearer token；修改 `.env` 后必须重建 panel 容器（而非重启）才能生效。
2. **DB 解密**：微信 DB 仅兼容 Linux WeChat 4.x，使用 SQLCipher 4 参数（AES-256-CBC、PBKDF2-HMAC-SHA512、256000 rounds、mac_salt = salt XOR 0x3A）。解密能力保留在 `drivers/wechat/` 内，不提前抽象。
3. **ptrace**：自动提取 key 需要容器具备 `SYS_PTRACE` capability 或 `--privileged`，且宿主 `ptrace_scope=0`。
4. **DB 路径**：自动检测写死 `/config/xwechat_files/<wxid>/db_storage/message/message_0.db`；改为 driver 内部常量，不作为框架公共假设。
5. **发送定位**：微信 `send_text/send_file` 使用 `display_name`（备注/昵称）而非 wxid 搜索会话，该行为由 `WechatDriver` 封装，不进入框架公共层。
6. **容器启动**：当前 [docker/autostart](../docker/autostart) 只启动一个应用；bridge 框架不假设自己能启动多个应用，生命周期控制仍由 panel/docker 负责。

---

## 7. 资源调度与并发

### 7.1 X11 资源争用对策

| 资源 | 争用场景 | 对策 |
|------|---------|------|
| 窗口焦点 | A 激活窗口时 B 失焦 | 所有 activate/key/click 经 UIScheduler 串行 |
| 剪贴板 | A 写入后 B 未读就被 C 覆盖 | paste 操作是"写剪贴板+立即 Ctrl+V"原子组，经 UIScheduler 串行 |
| 键盘事件 | A 的 Ctrl+V 发到 B 窗口 | activate 后立即操作，UIScheduler 保证原子 |
| 鼠标坐标 | A 的 click 坐标被 B 的 mousemove 改变 | mousemove + click 必须在同一个 UIScheduler 任务内 |

### 7.2 并发模型

```
HTTP 请求 (asyncio)
   ↓
driver 方法 (async)
   ↓
UIScheduler.submit(coro)  ◀── 全局串行点（X11 操作）
   ↓
XdotoolBase._run (asyncio subprocess)
```

- HTTP 层并发：FastAPI 默认并发处理请求
- driver 层并发：每个实例的方法可并发调用
- UIScheduler 层串行：所有 X11 写操作排队执行
- DB 读取层并发：不涉及 X11，独立并发（受限于 SQLite 读写锁）

### 7.3 限流策略

| 层级 | 限流 | 配置 |
|------|------|------|
| HTTP 全局 | 单 IP 每秒 N 请求 | `WOC_BRIDGE_MAX_CALLS_PER_SEC` |
| 单实例发送 | 发送类操作最小间隔 | `WOC_BRIDGE_SEND_DELAY_MS`（每实例独立） |
| UIScheduler | 操作间最小间隔 | 固定 100ms（防止 X server 压力） |
| UIScheduler 队列长度 | 最多排队 N 个操作 | 50，超限返回 `UI_BUSY` |

---

## 8. 应用生命周期

### 8.1 实例状态机

```
                  register
       ┌─────────────────────────┐
       │                         ▼
   ┌────────┐  start   ┌─────────┐  running   ┌────────┐
   │registered├────────►│starting├────────────►│running │
   └────────┘          └────┬───┘             └───┬────┘
       ▲                    │ fail                 │ stop
       │                    ▼                      ▼
       │                ┌────────┐            ┌────────┐
       │                │crashed │            │stopped │
       │                └───┬────┘            └───┬────┘
       │                    │ restart            │ start
       │                    ▼                    │
       └──────────────────────────────────────────┘
```

### 8.2 启动流程

1. `POST /api/apps` 注册实例 → 状态 `registered`
2. `POST /api/apps/{app_id}/start` → 状态 `starting`
3. driver.install()（首次启动，下载/解压）
4. driver.start()（启动应用进程）
5. 轮询 driver.find_window() 直到窗口出现（超时 60s）
6. 状态 `running`
7. 启动失败 → 状态 `crashed`，记录错误

### 8.3 自动启动

容器启动时，AppManager 读取 `/config/apps.json`，对所有 `auto_start=true` 的实例按顺序执行启动流程。

**顺序约束**：实例间启动间隔 5 秒（避免同时启动多个 GUI 应用导致内存峰值）。

### 8.4 健康检查

AppManager 后台任务（每 30s）：

1. 遍历所有 `running` 状态实例
2. 调用 `driver.is_running()`
3. 失败则状态转 `crashed`
4. 记录崩溃次数与时间
5. 若配置了自动恢复且未超限（默认 3 次/小时），触发 restart

### 8.5 数据卷布局

```
/config/
  apps.json                          实例注册表
  .woc-default-app                   默认实例 app_id（兼容层用）
  apps/
    wx_a1/
      data/                          应用数据根（driver 自由组织）
        wechat/
          opt/wechat/wechat          微信二进制
          xwechat_files/             微信运行时数据
      logs/                          该实例日志
      state/                         该实例状态文件
    xhs_b2/
      data/
        chromium/                    小红书专用 chromium user-data-dir
          Default/
            Cookies                  小红书登录 cookie
      logs/
      state/
```

---

## 9. 应用间通信

### 9.1 显式通信（推荐）

应用之间通过外部调用方（如面板或用户脚本）显式编排：

```
# 伪代码：从小红书采集内容，发到微信
xhs_note = await bridge.get(f"/api/apps/xhs_b2/xhs/search?keyword=foo")
await bridge.post(f"/api/apps/wx_a1/send/text", json={"to":"文件助手","content":xhs_note})
```

**优点**：流程显式、可观测、可调试。

**不提供应用间直接调用**：driver 之间不互相感知，避免隐式依赖。

### 9.2 事件总线（未来扩展）

可选的发布订阅机制：

```python
# driver 发布事件
await self.ctx.event_bus.publish("message_received", {"app_id": "wx_a1", "msg": {...}})

# 外部订阅
GET /api/events/stream  (SSE)
```

P0 不实现，留待有明确需求时再做。

---

## 10. 部署模型

### 10.1 单容器多应用

```
容器 woc-app-multi
  ├─ bridge (:8088)        管理 2 个实例
  ├─ Xvfb (:1)
  ├─ KasmVNC (:3000)
  ├─ 微信进程 (wx_a1)
  └─ chromium 进程 (xhs_b2)
```

适用于：单用户、资源受限（NAS）、应用间需要协同（同一桌面）。

### 10.2 多容器单应用（现有模式保留）

```
容器 woc-wx-<id1>   微信实例 1
容器 woc-wx-<id2>   微信实例 2
容器 woc-xhs-<id3>  小红书实例
```

适用于：多用户隔离、横向扩展、单应用崩溃不影响其他。

### 10.3 混合模式

面板支持两种模式并存：

- 轻量场景用单容器多应用（省资源）
- 隔离场景用多容器单应用（强隔离）

面板层提供创建实例时的"部署位置"选项：新建独立容器 or 加入已有容器的 bridge。

---

## 11. 演进路径

### S0：当前现状

- [bridge/server.py](../bridge/server.py) 约 2400 行单文件，全局单例 `_state`。
- 所有 `/api/*` 路由直接写死微信逻辑，无 `app_id`、无 driver 抽象。
- [docker/autostart](../docker/autostart) 一容器一应用；panel 通过 Docker API 管理生命周期。

### S1：抽 core/ 与 driver 抽象（不改行为，本阶段重点）

**目标**：把 server.py 拆成 `core/` + `drivers/wechat/`，建立 `AppDriver` 抽象，但保持单应用模式。

**改动**：

- 新建 `core/` 目录，迁移 `config` / `context` / `errors` / `logging` / `models` / `send_queue` / `screenshot`。
- 抽 `XdotoolBase`（通用 X11 操作）到 `core/xdotool_base.py`。
- 定义 `drivers/base.py` 的 `AppDriver` 抽象基类。
- 微信逻辑迁移到 `drivers/wechat/`，`WechatDriver(AppDriver)` 用组合方式持有 `XdotoolBase`。
- 微信路由迁移到 `drivers/wechat/routes.py`。
- `main.py` 读 `WOC_APP_TYPE`，只注册一个 driver，创建一个 `AppInstance`（`app_id="default"`）。

**验证**：所有现有 API 行为不变，面板无感知；`pytest` 或现有集成测试全部通过。

### S2：新路由层 + 向后兼容层

**目标**：引入 `/api/apps/{app_id}/*` 路由，旧路由作为 alias 转发。

**改动**：

- 新增 `/api/apps/{app_id}/status`、`/api/apps/{app_id}/send/text` 等路由。
- 旧路径 `/api/messages`、`/api/send/text` 等转为 alias，转发到 `/api/apps/default/...`。
- `/api/status` 同时返回旧字段（取 default app）与新 `apps` 数组。
- 旧字段日志打 `DEPRECATED` warning。

**验证**：面板与外部调用方无需改动即可工作；新路径可通过 `/api/apps/default/*` 访问。

### S3：UIScheduler 与 SendQueue 正交化

**目标**：解决多实例共享 X server 时的焦点/剪贴板冲突。

**改动**：

- 新增 `core/ui_scheduler.py`，所有 X11 写操作经其串行执行。
- 每个 `AppInstance` 持有独立 `SendQueue`；`UIScheduler` 全局唯一。
- 单应用模式下行为不变，多实例模式下才体现串行价值。

**验证**：高并发调用 `/api/apps/default/send/text` 不再出现剪贴板污染。

### S4：AppManager 与单容器多实例（未来扩展）

**目标**：支持容器内注册多个 `AppInstance`。

**改动**：

- 实现 `core/app_manager.py`、`core/app_instance.py`、`core/app_kind.py`。
- 新增 `/api/apps` CRUD 路由。
- `/config/apps.json` 持久化。
- 需同步改造 [docker/autostart](../docker/autostart) 以支持启动多个应用。

**验证**：可在同一容器内通过 API 注册并启动微信 + 小红书两个实例。

### S5：新增 Xiaohongshu/Telegram Driver（框架可扩展性验证）

**目标**：落地非微信应用自动化。

**改动**：

- 实现 `drivers/xiaohongshu/driver.py` + `routes.py`（P0：publish/text、search）。
- 或实现 `drivers/telegram/driver.py`（优先浏览器/桌面版，若走原生协议则另议）。

**验证**：通过 `/api/apps/{app_id}/xhs/publish` 成功发笔记。

### S6：面板适配与单容器多应用部署（不在本文档范围）

**目标**：面板支持多应用管理 UI 与"单容器多应用"部署选项。

**改动**（panel 层）：

- 面板新增"应用实例"管理页。
- 桌面入口支持选择"进入哪个应用窗口"。
- 实例创建支持"新建独立容器"或"加入已有容器"。

### S7：下线兼容层

**目标**：移除 alias，完成迁移。

**前提**：面板与所有已知适配器已切换到新路径。

**改动**：

- 删除旧路径 alias。
- `/api/status` 移除旧字段。
- major 版本升级到 v3.0.0。

---

## 12. 不做的事

### 12.1 完全不做

1. **不引入应用间隐式依赖**：driver 之间不互相调用，编排由外部完成。
2. **不做多容器 bridge 集群**：一个 bridge 进程只管一个容器内的应用，跨容器调度由面板负责。
3. **不做应用沙箱**：应用间共享 X server 与文件系统（受 Linux 权限控制），不做额外隔离。
4. **不做 RBAC**：`app_id` 级别的权限控制留给面板层，bridge 层只做 Bearer Token 全局鉴权。
5. **不改 KasmVNC 串流层**：桌面串流保持现状（全屏共享），不按应用切分串流。
6. **不做 OCR**：不引入 tesseract，页面识别靠 URL + 坐标启发式。

### 12.2 本阶段先不做

1. **不抽象 SQLCipher 解密**：解密能力暂留 `drivers/wechat/`，待第二个 SQLCipher 应用出现再抽公共层。
2. **不实现单容器多应用启动**：[docker/autostart](../docker/autostart) 当前只启动一个应用，改造它需要 panel/docker 同步调整，不在本阶段 bridge 文档范围。
3. **不新增非微信 driver**：先完成框架抽象与微信 driver 迁移，再落地 Xiaohongshu/Telegram driver。
4. **不替换全局 `SendQueue`**：S1 保持现有全局队列，S3 再下放到每个 `AppInstance`。
5. **不引入新依赖**：仍只使用 fastapi / uvicorn / pydantic / pillow / cryptography。

---

## 13. 关键决策记录

| 决策点 | 选择 | 理由 |
|--------|------|------|
| 文档定位 | 目标架构 + `[现状]` 注释 | 既描述最终形态，又不掩盖当前代码未落地的事实 |
| 本阶段重点 | 先让 bridge 在"多容器单应用"模式下可工作 | docker autostart 当前只支持一容器一应用；先完成 bridge 框架抽象，再扩展单容器多应用 |
| 文档范围 | 只聚焦 bridge 层 | panel UI、docker 多应用启动机制另行设计 |
| 抽象颗粒度 | 一应用一 driver（粗粒度） | 业务流程差异大，能力复用价值低 |
| 容器内应用数 | 目标为多实例并存，当前先单实例 | 满足"一容器多应用"长期需求，但本阶段保留单实例运行 |
| 路由前缀 | `/api/apps/{app_id}/*` + alias 过渡 | 实例独立寻址 + 向后兼容 |
| XdotoolBase 风格 | 组合（driver 持有 `self.xd`） | 避免多继承混乱 |
| SQLCipher 解密位置 | 留 drivers/wechat/ | YAGNI，等第二个应用再抽 |
| X11 并发控制 | 全局 UIScheduler 串行 | 多应用争用同一 X server 必须串行；当前尚未实现 |
| 应用间通信 | 显式编排（无直接调用） | 流程可观测、可调试 |
| 部署模型 | 单容器多应用 + 多容器单应用并存 | 兼顾资源效率与隔离性；当前仅多容器单应用可用 |
