# PLAN.md — openclaw-proxy

## 项目定位

轻量级异步 WebSocket 网关，部署在 Linux 云服务器上，负责在 **小智 ESP32 语音硬件** 与 **OpenClaw 智能体** (`/root/.openclaw/`) 之间做协议翻译和管线编排。

```
┌──────────────┐  Opus/JSON   ┌──────────────────┐   CLI    ┌──────────────┐
│  XiaoZhi     │◄────────────►│  openclaw-proxy  │────────►│  OpenClaw    │
│  ESP32       │  WebSocket   │  (FastAPI:8000)   │  stdout  │  Gateway     │
└──────────────┘              └──────────────────┘◄────────│  (:18789)    │
                                  │        ▲                └──────────────┘
                                  ▼        │
                              ┌──────┐ ┌──────┐
                              │ STT  │ │ TTS  │
                              └──────┘ └──────┘
```

**对接方式决策（2026-03-15）：** 采用方案 B — 通过 `openclaw agent` CLI 调用 gateway，
不修改 `.openclaw/` 目录，不注册 channel 插件。优先跑通演示。

## 协议约定

### 上行（ESP32 → Proxy）

| 帧类型 | 格式 | 说明 |
|--------|------|------|
| 文本帧 | `{"type": "hardware_event", "action": "wake_word\|key_down\|key_up"}` | 硬件状态事件 |
| 二进制帧 | Raw Opus bytes | 用户语音音频流 |

### 下行（Proxy → ESP32）

| 帧类型 | 格式 | 说明 |
|--------|------|------|
| 文本帧 | `{"type": "state", "status": "listening\|thinking\|speaking"}` | 控制硬件 LED 状态 |
| 文本帧 | `{"type": "text_reply", "text": "..."}` | LLM 文本回复 |
| 二进制帧 | Raw Opus bytes | TTS 合成音频流 |

## 分阶段实施计划

---

### Phase 0 — Mock 骨架搭建 ✅ 已完成

**目标：** 跑通 WebSocket 握手和完整数据通路，用假数据验证时序正确。

**交付物：**
- [x] `main.py` — FastAPI + WS `/chat` 端点，帧路由，三段 mock 管线
- [x] `tests/test_main.py` — 8 个测试全部通过（3 单元 + 5 集成）
- [x] `requirements.txt` — 依赖声明

---

### Phase 0.5 — OpenClaw 真实对接 ✅ 已完成

**目标：** 将 `call_openclaw` 从 mock 替换为真实 CLI 调用，跑通文本对话。

**决策：** 采用 `openclaw agent --agent main --session-id {id} -m {text} --json` 命令。
Gateway 运行在 `localhost:18789`，CLI 自动连接。

**交付物：**
- [x] `call_openclaw` 通过 `asyncio.create_subprocess_exec` 调用 CLI
- [x] 每个 WebSocket 连接分配独立 `session_id`，支持多轮对话
- [x] LLM 推理期间每 3 秒发送 thinking 心跳，保持硬件活跃
- [x] CLI 失败 / 空 payload 的错误处理
- [x] `tests/test_main.py` — 10 个测试全部通过（mock subprocess）

**管线流程：**
```
Binary frame → process_stt(mock) → send "thinking" state
            → call_openclaw(CLI) → heartbeat every 3s → send text_reply
            → process_tts(mock) → send binary audio
```

---

### Phase 1 — 音频缓冲与真实 ESP32 握手

**目标：** 支持 ESP32 的真实交互模式——用户按住按键持续说话，松开后触发管线。

**任务：**
- [ ] 实现 `AudioBuffer` 类，按连接维护音频缓冲区
  - `key_down` / `wake_word` → 开始缓冲，进入 "listening" 状态
  - 后续二进制帧 → 追加到缓冲区
  - `key_up` → 停止缓冲，将完整音频交给管线
- [ ] 重构 `handle_binary_frame` 为缓冲追加逻辑
- [ ] 新增 `handle_pipeline(ws, audio_buffer)` 统一编排三段管线
- [ ] 处理边界情况：空音频、超长音频、重复 key_down
- [ ] 补充对应测试用例

**关键数据结构：**
```python
class SessionState(BaseModel):
    status: Literal["idle", "listening", "processing"] = "idle"
    audio_chunks: list[bytes] = []
```

---

### Phase 2 — 真实 STT 接入 ✅ 已完成（2026-03-16）

**目标：** 将 `process_stt` 从 mock 替换为真实语音识别。

**决策：** 选用 OpenAI Whisper base 模型，本地 CPU 推理。

**交付物：**
- [x] Whisper base 模型懒加载（`_get_whisper_model` 单例）
- [x] Opus → tmpfile → Whisper transcribe（`asyncio.to_thread` 非阻塞）
- [x] 固定 `language="zh"` 中文识别
- [x] 测试：mock Whisper 模型验证临时文件创建、语言参数、文本 strip

---

### Phase 3 — ~~OpenClaw 智能体对接~~ ✅ 已在 Phase 0.5 完成

已通过 `openclaw agent` CLI 完成对接，无需额外工作。
后续可考虑升级为 channel 插件获得更紧密集成。

---

### Phase 4 — 真实 TTS 接入 ✅ 已完成（2026-03-16）

**目标：** 将 `process_tts` 从 mock 替换为真实语音合成。

**决策：** 选用 edge-tts（微软 Neural TTS），MP3 → ffmpeg → Opus 编码。

**交付物：**
- [x] edge-tts `Communicate.stream()` 异步收集 MP3 音频
- [x] ffmpeg 管道转码 MP3 → Opus（libopus, 24kbps, 16kHz, mono）
- [x] ffmpeg 失败时抛出 RuntimeError
- [x] 测试：mock edge-tts stream + ffmpeg subprocess，验证 MP3 拼接和错误处理
- [x] 11 个测试全部通过

> **详细记录见：** `MILESTONE-STT-TTS.md`

**遗留（留给 Phase 5）：**
- [ ] 实现分块推流：将长音频切成 ≤960 字节 Opus 帧逐帧下发
- [ ] 推送 `{"type": "state", "status": "speaking"}` 状态通知

---

### Phase 5 — 生产加固

**目标：** 具备长期稳定运行的能力。

**任务：**
- [ ] 连接管理：最大并发数限制、心跳超时断开、异常重连
- [ ] 配置外置：`config.yaml` 或环境变量管理端口、模型选择、API 密钥等
- [ ] 结构化日志：JSON 格式日志，接入日志收集
- [ ] 指标监控：`/metrics` 端点暴露连接数、管线延迟、错误率
- [ ] systemd 服务单元文件，支持开机自启和自动重启
- [ ] HTTPS/WSS 支持（通过 nginx 反代或 uvicorn SSL 参数）
- [ ] HTTP 管理接口：`GET /sessions` 查看在线连接、`POST /broadcast` 广播消息

---

## 技术栈总览

| 层 | 当前 | 目标 |
|----|------|------|
| 框架 | FastAPI + uvicorn | 不变 |
| WS 协议 | Starlette WebSocket | 不变 |
| 数据校验 | Pydantic v2 | 不变 |
| STT | Whisper base (本地 CPU) | 不变（可选升级 small/medium） |
| LLM | `openclaw agent` CLI → Gateway :18789 | 同左（可选升级为 channel 插件） |
| TTS | edge-tts → ffmpeg → Opus | 不变（可选 CosyVoice） |
| 音频编解码 | ffmpeg (libopus) | 不变 |
| 部署 | `python main.py` | systemd + nginx |

## 文件结构规划

```
openclaw-proxy/
├── main.py              # FastAPI 应用入口、WS 端点、帧路由
├── pipeline/
│   ├── __init__.py
│   ├── stt.py           # process_stt 实现
│   ├── llm.py           # call_openclaw 实现
│   └── tts.py           # process_tts 实现
├── models.py            # Pydantic 模型集中定义
├── session.py           # 连接会话状态管理、音频缓冲
├── config.py            # 配置加载（环境变量 / yaml）
├── tests/
│   ├── test_main.py
│   ├── test_stt.py
│   ├── test_llm.py
│   └── test_tts.py
├── requirements.txt
├── CLAUDE.md
└── PLAN.md
```

> 注：Phase 0 阶段全部逻辑集中在 `main.py`，从 Phase 1 开始按上述结构拆分模块。
