编辑 | blame | 历史 | 原始文档

Phase 1 Completion Review

Date: 2026-08-31
Basis: current code (not the original Phase 1 wish-list)
Compared against: phase-1.md · Master Plan · Architecture Audit · Client Plan V1.1 · Server Plan V1.1
Repositories: xiaozhi-esp32-2.2.6 (Client) · xiaozhi-esp32-server (Server)

本文件只记录事实、差异、债务与对 Master Plan 的修正建议。不包含下一阶段的详细编码任务(见 phase-2-plan.md)。


1. 当前代码盘点(以仓库为准)

1.1 Phase 1 实际改动的文件

Client(xiaozhi-esp32-2.2.6)

文件 角色
main/boards/sening-xiaozhi-s3/app_task.h 新增。 设备侧 Task 启动 / pending / 离线口令 / 复读记忆
main/boards/sening-xiaozhi-s3/app_task.cc 新增。 Board_StartAppTask、优先级、NVS pending、last_rep、技能分发
main/boards/sening-xiaozhi-s3/mqtt.c START_APP_TASK;SET_ALARM 可附带 app_id/task_id/params 写入 pending
main/boards/sening-xiaozhi-s3/sening_xiaozhi_s3.cc MCP self.mp3.play 增加 repeat / power_off_after / 空 name 回退;开机 Board_ResolveBootOrRestore
main/boards/sening-xiaozhi-s3/mp3_player.cc 整首循环 N 次;自然结束才 power_off;repeat_left
main/boards/sening-xiaozhi-s3/system.h / system.c 离线口令宏;SDCard_Play_MP3_With_Speed_Repeat
main/audio/wake_words/afe_wake_word.cc MultiNet 注册 4 条离线口令
main/audio/wake_words/custom_wake_word.cc 同上
main/application.cc SendListenDetect:没网且通道未开时不 WakeWordInvoke

未改:Opus 热路径、DeviceState 迁移表、hello/listen/tts/stt 字段含义。非 Sening 板无 App Task 执行器。

Server(xiaozhi-esp32-server,仅 Python xiaozhi-server)

文件 角色
core/app_runtime/__init__.py 新增模块。
core/app_runtime/registry.py 加载 apps.json(bundled;可选 data/apps.json)
core/app_runtime/apps.json App / Task 注册表
core/app_runtime/runtime.py start_app_task / switch_app / mode 分流 / 口令 / 跟读轮次 / 翻译
plugins_func/functions/switch_app.py LLM 可调的切 App
plugins_func/functions/start_repeater.py 打开复读机
plugins_func/functions/start_shadowing.py 打开跟读
plugins_func/functions/translate.py 连接 LLM 翻译(智控台 Key)
mcp_servers/translate/server.py 独立 translate MCP(回退)
mcp_server_settings.json 注册 english-translate
core/connection.py 连接内存:active_app_id / interaction_mode / tool_policy 等
core/handle/receiveAudioHandle.py startToChat 前按 mode 分流;口令优先于 LLM
core/handle/textHandler/listenMessageHandler.py [app_task] / [voice_entry]
core/providers/tools/unified_tool_manager.py filter_tools 白名单
config.yaml 加载 switch_app / start_repeater / start_shadowing

manager-api / manager-web

无 App Runtime 相关改动。 无新表、无新 REST。智控台仍只管理人设 / 插件绑定。
(历史对话里 manager-web 的 vue.config.js / teleport 修复与 Phase 1 业务无关,不计入本阶段架构。)

1.2 实际增加的模块

模块 位置 职责
App Runtime Server core/app_runtime/ 注册表 + 统一 start_app_task + mode 行为
Device Task Gateway Client app_task.cc 三通道汇合、抢占、pending、本地 Skill 执行
Translate plugin + MCP Server plugins + mcp_servers/translate 翻译旁路 chat()
Repeater / Shadowing plugins Server plugins 进入对应 Task

没有:Task Runtime 状态机类、Step 引擎、Assessment 服务、Scheduler 服务、App Store。

1.3 实际增加的 API

类型 名称 说明
REST 无 manager-api 未增接口
Python 插件 switch_app app_id + 可选 task_id
Python 插件 start_repeater 可选 name/repeat/power_off_after
Python 插件 start_shadowing 无参数
Python 插件 translate text + 可选语言
Device MCP self.mp3.play 扩展 repeat、power_off_after;name 可空
Device MCP self.mp3.get_status 已有;增加 repeat_left

1.4 实际增加的协议

没有新的 Xiaozhi WS type。热路径语义冻结得到遵守。实际采用的是「复用 listen detect 文本」和「色宁 MQTT 新 action」。

通道 载荷 用途
WS listen state=detect [app_task]{app}\|{task}\|{source} 设备通知 Server 进入某 Task(execute_skills=false,避免复读双播)
WS listen state=detect [voice_entry] 离线「切换模式」且**已联网**时要菜单
MQTT snlp/v1/.../cmd action=START_APP_TASK 通道①即时启动
MQTT SET_ALARM 可选 app_id/task_id/params 写入设备 pending,通道②

未增加:type=task、mode.set、play.completed 独立消息、speech.stopped。

1.5 实际增加的数据库结构

无。 App 状态在 ConnectionHandler 内存。设备 pending / last_rep 在 NVS。WS 断线后 Server 回到 default chat;设备 pending 仍在本地。

1.6 Client 架构变化

Phase 1 之前(Audit):

Application 单例 + DeviceState + Opus 双工 + MCP
Sening 旁路:self.mp3.* / snlp MQTT PLAY_MEDIA / RTC 闹钟
离线「切换模式」只打日志

Phase 1 之后:

Application(Chat 热路径不变)
  └── Board_StartAppTask(Sening 板级 Task Gateway)
        ├── source 优先级 mqtt_immediate > schedule > voice > default_chat
        ├── NVS pending + last_rep
        ├── 技能查找表 k_task_skills[](固件内硬编码 id,不是业务 if 树)
        └── 本地执行 self.mp3.play / 或只 notify Server
MultiNet:切换模式 / 复读机 / 开始复读 / 退出复读
没网:切换模式不连云;复读机可播上次音频或 SD 第一首

InteractionMode 没有成为固件枚举。设备不持久化 App 历史。DeviceState 仍只管听/说/升级。

1.7 Server 架构变化

Phase 1 之前:

ConnectionHandler.chat() 是唯一对话编排
Agent + Plugin + MCP
无 App / 无 InteractionMode

Phase 1 之后:

receiveAudioHandle.startToChat
  → 口令 match_voice_switch(先于 LLM)
  → mode 分流:
       PLAY_AUDIO 忽略上行
       SHADOWING  handle_shadowing_turn(不 chat)
       TRANSLATION run_translation(不 chat)
       BILINGUAL  wrap 后仍 chat()
       CHAT       现网 intent/LLM
App Runtime:apps.json + start_app_task
工具:inherit_agent | allowlist

权威状态仍在 当前 WS 连接内存,不是 TaskInstance 表。

1.8 Client ↔ Server 实际通信流程

① 小程序 / MQTT 调试客户端
     START_APP_TASK
          ↓ snlp MQTT
     Board_StartAppTask
          ├─ 本地 Skill(复读:SD MP3)
          └─ 若 Xiaozhi 通道已开:listen detect [app_task]
                    ↓
               Server start_app_task(execute_skills=false)
               更新 active_app_id / interaction_mode,TTS 播报

② SET_ALARM + pending
     到点 RTC 唤醒 → Board_ResolveBootOrRestore
     非 chat 计划 → 直接 StartAppTask(schedule)
     否则 → chat.conversation

③ 离线语音
     MultiNet 复读机 → 本地播放,不依赖 Server
     MultiNet 切换模式 → 仅 IsNetworkReady 时 [voice_entry]
     在线 ASR 短句 → match_voice_switch → start_app_task

④ Chat 热路径(未离开 CHAT)
     Mic → Opus → ASR → LLM → TTS → Speaker
     与 Phase 1 前相同

1.9 Task Runtime 当前实现状态

能力 状态
统一入口 start_app_task 有(Server + 设备各一份)
App / Task 注册表 有(Server JSON;固件另有技能表)
InteractionMode 分流 有(连接字符串,5 种 mode)
Step / StepInstance 无
TaskInstance 持久化 无
Event(play.completed 等) 无正式协议;播放结束设备会 [app_task]chat 通知回聊
跟读 有 Demo:TTS 范读 + ASR 轮次 + 回声抑制;无归档、无评分
Assessment / Metric 无

结论:这是 薄 App Runtime + 单 Task 动作,不是 Master Plan 的 Task Runtime。

1.10 测试情况

项 现状
phase-1.md §10 验收清单 文档存在,**仓库内无对应自动化用例**
xiaozhi-server pytest / unittest(App Runtime) 无
固件单元测试(app_task / MultiNet) 无
现有测试资产 仅 ASR/TTS/LLM performance_tester
实际验证方式 人工烧录 + 重启 Server + MQTT/口令联调

Phase 1 在工程上「做完了功能」,没有「测完了回归」。 这是进入 Phase 2 前最大的过程风险,不是功能缺口。


2. Phase 1 Implementation Review(计划 vs 代码)

2.1 对照表

项目 原计划(phase-1.md) 实际实现 差异原因 后续处理
薄 App 框架 Server JSON + active_app_id;设备无 App 目录 已实现;注册表在 core/app_runtime/apps.json — 保持;不要下发到 SD
Default chat.conversation 配网/开机进入并主动开口 pending 默认 chat;agent_proactive + 进入 CHAT 时 TTS 招呼 冷场再催促未做成可配循环 Phase 2 不优先;可配次数留债
三通道 + 优先级 MQTT > Schedule > Voice 设备 Board_AppTask_SourcePriority;Server SOURCE_PRIORITY 两端各算一次,可能短暂不一致 Phase 2 用 skill_event 对齐结束态即可,不合并 MQTT
START_APP_TASK 新 MQTT action 已实现 — 保持
pending + 闹钟唤醒 NVS;非 chat 计划不先闲聊 Board_ResolveBootOrRestore + SET_ALARM 写 pending Server 无 Schedule 权威表 接受;权威表放到 Scheduler Phase
离线「切换模式」 不再空 log;进菜单 有网才 [voice_entry];没网跳过 实施中发现 WakeWordInvoke 会干等 Server 正确修正,写入架构 V2
复读 N 次在设备 repeat 参数 mp3_player 循环 — 保持
power_off_after 自然结束才软关机 已实现 — 保持
翻译旁路 LLM chat 独立 MCP 主路径改为 conn.llm + translate 插件;MCP 仅回退 独立 MCP 依赖占位 Key,智控台 Key 才是现网 正确修正;MCP 可降级为可选
工具白名单 allowlist filter_tools — 保持
Xiaozhi type=task 明确不做 未做 — Phase 2 仍不做完整 type=task
Java 新表 明确不做 未做 — Phase 3 再做 Instance 表
三个 App chat / 复读 / 翻译 四个 App + 跟读 Task:另增 english_conversation、shadowing 产品要把跟读/中英对话尽早可演示 跟读仍是 Demo;正式采集是 Phase 2
固件无业务名 禁止 if (app == english) 固件 k_task_skills[] 含 app_id 字符串 MQTT 离线启动必须能查 Skill,无法只问 Server 接受为「配置表」;禁止再加英语评分/音频逻辑
口令表在 Assets JSON 映射表可扩 固件硬编码 4 条拼音;在线口令在 runtime.py MultiNet 容量与误触发 正确收窄;不要把翻译/跟读塞进 MultiNet
apps.json 路径 建议 data/apps.json bundled core/app_runtime/apps.json,可选 data 覆盖 部署更简单 保持
PLAY_AUDIO 忽略 ASR 是 是 — 保持
用户唤醒复位 chat 是;闹钟不复位 on_user_wakeup;[app_task] 不走唤醒 — 保持
无 play.completed 接受 播完设备直接切回 chat 并 detect Server 仍不知道第几遍 Phase 2 补 skill_event
小程序 UI 后置 未做 — Parent UI 很后
测试清单 详细 checkbox 无自动化、无书面跑测记录 赶功能 Phase 2 必须带 pytest + 手工回归表

2.2 哪些设计被修改(相对原文 Plan)

  1. 离线入口收窄:只保留「切换模式 + 复读机/开始复读/退出复读」。翻译/跟读/中英对话只走在线 ASR。
  2. 没网不连云:SendListenDetect 在无网且通道未开时直接 return。
  3. 复读可完全离线:NVS last_rep + SD 第一首;注册表仍只在 Server。
  4. 翻译实现:连接 LLM 优先于独立 MCP 进程。
  5. Mode 集合扩大:SHADOWING、BILINGUAL 提前落地(Client Plan 把 TRANSLATION/GAME 放在 Phase 3)。
  6. 跟读提前:不是 SD 范读 + 录音对象,而是 TTS 句子列表 + 文本回声过滤。
  7. 协议挂钩:用 [app_task] / [voice_entry] 寄生在 listen/detect,而不是新 type。

这些修改应视为 Phase 1 的真实架构,后续 Phase 不得假装「设备仍只有 CHAT+PLAY_AUDIO」。

2.3 哪些设计没有实现

  • 冷场多次主动开口(可配间隔)
  • 口令→id 的设备端 JSON/Assets 表
  • Server 侧 Schedule 权威存储
  • play.completed / 遍数上报
  • 跟读音频对象与 step 关联
  • Assessment
  • 非法 app_id 的系统化错误码(MQTT 有 ack;语音多为 TTS 句子)
  • Phase 1 测试计划的自动化
  • 非 Sening 板的复读

2.4 实施中发现的问题

  1. self.mp3.play 立即 accepted,Server 无法编排 N 次循环 → 循环必须在设备(Plan 已预见,代码证实)。
  2. 独立翻译 MCP 的 yaml Key 与智控台 Key 分裂 → 改走 conn.llm。
  3. ASR 常把结果包成 {content,language,emotion} → 必须拆 content,否则翻译/口令失败。
  4. 跟读/翻译时喇叭回声会当用户插话 → AEC 打断仅限 CHAT/BILINGUAL;跟读再加文本相似度过滤。
  5. 「切换模式」在没网时 WakeWordInvoke 会挂起 → 必须短路。
  6. 设备本地启动复读后再通知 Server,若 execute_skills=true 会 播两遍 → [app_task] 必须 execute_skills=false。
  7. MultiNet 词表不能塞满业务口令,否则误触发且没网也无法跑翻译。

2.5 技术债务(实施产生)

见第 5 节分级。核心:双技能表、寄生协议、无测试、跟读不是音频任务、App 状态不落库。

2.6 原先没考虑到、但已出现的问题

  • 用户口语「模式」与内部 InteractionMode / App 命名冲突(已规定:对用户仍说「模式」,对代码仍用 App/Task)。
  • 离线复读与在线权威状态会分叉:没网播完后 Server 仍以为在 CHAT。
  • 固件必须认识 english_learning 才能离线执行,与「业务名不上固件」理想不完全一致。
  • manager-web 开发构建问题(teleport/usedExports)与 App 无关,但说明控制台栈也有债,不要在 Phase 2 顺手大改。

3. 与 Master Plan 核心抽象对照

抽象 当前是否存在 实现位置 完成程度 是否需要调整 未来是否扩展
App 是(薄) Server apps.json + active_app_id;设备技能表有 app_id 连接级容器,无安装/订阅/资源包 保持「Server 权威、不下发 SD」 Phase 6 再做资源与清单
Task 是(薄) apps.json tasks + start_app_task 一个 Task ≈ 一种动作,无 Step 图 不要把跟读 Demo 当成完整 Task Definition Phase 3 起 JSON Step
Step 否 — 0% Phase 2 不要先上完整 Step 引擎 Phase 3+ 跟读一步一 Step
Skill 是(MCP + 板级) 设备 MCP;Server 调 DeviceMCP;固件 k_task_skills 能跑 play/stop/power_off/translate 双表必须当契约维护 核心 play_audio 平台化后移
InteractionMode 是(Server 字符串) conn.interaction_mode CHAT/PLAY_AUDIO/TRANSLATION/SHADOWING/BILINGUAL 固件仍无 Mode 枚举,可保持正交 正式 FOLLOW_READ 可与 SHADOWING 合并命名
TaskInstance 否 — 0% Phase 2 只用内存/文件 Session,不建 Java 表 Phase 3 落 manager-api
StepInstance 否 — 0% 同上 跟读采集后才有意义
Event 部分 [app_task] / [voice_entry];MQTT ack 无统一 Event 模型 Phase 2 增加 [skill_event] 即可,不上第四套总线 以后再考虑 type=task
Audio 聊天附件级 ai_agent_chat_audio LONGBLOB;跟读未强制归档 跟读轮次未必走 report Phase 2 必须把跟读 ASR/音频挂上 Session 再迁对象存储
Assessment 否 — 0% 不要在 Phase 2 做评分 Phase 4 文本比对 v0
Metric 否 — 0% — Assessment 之后
Schedule 半截 设备 RTC + pending;无 Server 日历 能 7 点唤醒复读 不要用 RTC 冒充家长计划权威 Phase 5 Server Scheduler
Plan 否 — 0% — 家长任务管理
Subscription 否 — 0% — 平台后期

符合 Master Plan 的方向: 权威在 Server、设备执行、Chat 热路径保留、App 不是固件、复读不走 play_music。

不符合或提前/滞后:

  • Server Plan 说 Phase 1 可以没有 App 实体 → 实际做了薄 App,这是 有益提前,应写进 Master Plan。
  • Client Plan 说 Phase 1 甚至不需要 activeAppId → 实际设备有 pending app_id,Server 有 active_app_id。
  • 跟读/中英对话/翻译 Mode 提前 → Master Plan 的 Phase 边界要按代码改,而不是把已落地功能假装「未开始」。
  • TaskInstance / Assessment / Scheduler 仍未开始 → 与 Master Plan 一致,不要在下一阶段一次做完。

4. Current Architecture V2(基于代码)

4.1 Current Client Architecture V2

ESP32-S3 (sening-xiaozhi-s3)
├── Application::Run                    DeviceState: Idle/Listening/Speaking/...
│     ├── AudioService                  Opus 16k 热路径(未改语义)
│     ├── Protocol (Xiaozhi WS/MQTT)    hello/listen/abort/tts/stt/mcp
│     └── MCP Server                    self.mp3.* / self.power_off / alarm / ...
│
├── WakeNet + MultiNet
│     口令: 切换模式 | 复读机 | 开始复读 | 退出复读
│     → Board_Handle_Voice_Command
│
└── Task Gateway (app_task.cc)          ← Phase 1 新增
      Board_StartAppTask(app, task, params, source)
      ├── 优先级抢占 / preempt skills
      ├── NVS pending + last_rep
      ├── k_task_skills[] 查 Skill
      └── 执行: SD MP3 循环 | notify Server | 空 Skill 只切态

旁路(仍并存):
  snlp MQTT  START_APP_TASK / SET_ALARM / PLAY_MEDIA / DOWNLOAD
  DS3231 RTC 闹钟唤醒 → OnRestoredFromPowerOff → ResolveBootOrRestore

非 Sening 板:无 Task Gateway;翻译等纯 Server Mode 仍可走 Xiaozhi WS。

4.2 Current Server Architecture V2

xiaozhi-server (Python)
├── ConnectionHandler
│     session_id (WS 连接级,≠ TaskInstance)
│     active_app_id / current_task_id / interaction_mode / tool_policy
│     shadowing_index / shadowing_prompt
│
├── receiveAudioHandle.startToChat
│     1. match_voice_switch
│     2. mode 分流(不进 chat 的: PLAY_AUDIO / SHADOWING / TRANSLATION)
│     3. CHAT / BILINGUAL → intent + LLM
│
├── app_runtime
│     registry(apps.json)
│     start_app_task / switch_app / filter_tools
│     handle_shadowing_turn / run_translation
│
├── Plugins: switch_app, start_repeater, start_shadowing, translate
├── Device MCP executor → self.mp3.play/stop
└── 可选 Server MCP translate 进程

manager-api (Java)     用户/设备/Agent/聊天 WAV   ← 未参与 App Runtime
manager-web            智控台                     ← 未参与

4.3 Client ↔ Server Architecture V2

                    ┌──────────── 小程序 / MQTT 客户端 ────────────┐
                    │  START_APP_TASK / SET_ALARM                 │
                    └──────────────────┬──────────────────────────┘
                                       │ snlp MQTT
                                       ▼
┌──────── Client ────────┐     Xiaozhi WS      ┌──────── Server ────────┐
│ Task Gateway           │◄── listen detect ──►│ App Runtime            │
│  [app_task] [voice_entry]                    │  start_app_task        │
│ SD MP3 Skill           │◄── type=mcp ───────►│  DeviceMCP play/stop   │
│ Chat AudioService      │◄── opus/tts/stt ───►│  ASR / LLM / TTS       │
│ pending NVS            │                     │  connection memory     │
└────────────────────────┘                     └──────────┬─────────────┘
                                                          │ 现有 report
                                                          ▼
                                                   manager-api
                                                   chat_history + audio BLOB

同一时刻:一个设备一个 active App/Task。Chat 与 Task 音频互斥沿用 PauseSdCardMp3。


5. 技术债务

P0 — 不解决就无法安全继续开发

ID 债务 立即修?
P0-1 无 App Runtime 自动化测试,Phase 2 极易回归 Chat / 口令 / 复读双播 是。 Phase 2 第一刀就是 pytest + 手工回归表,否则不准加表/加协议
P0-2 跟读 Demo 被误当成「已有 FOLLOW_READ 采集」——没有 prompt/asr/audio 绑定,Assessment 无处落点 是(作为 Phase 2 目标)。 允许暂时存在评分,不允许继续假装已采集

P0 不是「系统不能跑」,而是「下一阶段会建在流沙上」。

P1 — 近期应该解决

ID 债务 立即修?
P1-1 无 play.completed:Server 靠设备主动切回 chat Phase 2 用 [skill_event],不要新 WS type
P1-2 双技能表:apps.json vs k_task_skills[] 会漂 Phase 2 写契约检查清单;不同步下载 JSON
P1-3 寄生协议 [app_task] 与唤醒词/ASR 文本空间重叠 保持前缀约定;文档化;Phase 2 加 skill_event 前缀
P1-4 离线复读与 Server 连接态分叉 允许暂时存在;重连后以设备 notify 或下次 detect 对齐
P1-5 跟读范读走 TTS 而非音频音频,和复读资源模型不一致 Phase 2 可先采集 TTS 跟读;SD 范读放到资源 Phase
P1-6 翻译双实现(plugin + MCP) 允许暂时存在;Phase 2 不删 MCP,测试以 plugin 为准
P1-7 BILINGUAL 仍走 chat(),靠 prompt 约束,易夹杂语言 允许暂时存在;不是 Phase 2 主路径

P2 — 平台化时再解决

ID 债务 立即修?
P2-1 双 MQTT(Xiaozhi vs snlp) 否
P2-2 App 状态不落库 否(Phase 3 Instance)
P2-3 复读仅 Sening 否
P2-4 聊天 WAV LONGBLOB 否
P2-5 smartlearning 课程云未吸收 否(Phase 6)
P2-6 固件认识业务 app_id 字符串 否;保持查找表,禁止业务分支膨胀
P2-7 无 type=task / mode.set 否;等 Instance 真正需要步进再加
P2-8 小程序 / 家长日历 UI 否

6. Master Plan 再审视

已完成(相对长期愿景的「第一刀」)

  • Chat 作为 default App/Task 活下来
  • 设备可执行非 Chat 的播放 Skill
  • Server 可按 Mode 跳过 LLM
  • 三通道进入 Task 的骨架
  • App 与 Agent 分离(Agent 仍是人设;App 是 apps.json)

正在进行

  • 英语学习:复读可用;跟读仅 Demo
  • 翻译 / 中英对话:可用,未产品化评估

需要调整的设计

  1. 「Phase 1 不做 App」作废。 薄 App Runtime 已是 Current Architecture V2 的核心,后续只加深,不推倒重来。
  2. 「跟读 = Client Phase 2 才出现」作废。 Mode 和口令已在;缺的是采集与 Instance。
  3. 「用 type=task 才能做 Task」暂缓。 寄生 detect + MQTT 已够启动;先补 Event 与 Session,再考虑新 WS type。
  4. 「apps 下到 SD」明确拒绝。 与 Phase 1 后期结论一致,Master Plan 的 App Sync 改为「资源下发、定义留云」。
  5. 离线策略写入 Master Plan: MultiNet 只保留总入口 + 可离线执行的 Skill(复读);云能力口令走 ASR。

已不合理或应降优先级

  • 第一阶段就上 QUIZ/GAME 完整 Mode
  • Phase 2 一次做完 Task 表 + 评分 + 对象存储 + 新协议
  • 合并双 MQTT 作为近期目标
  • 把设备建成完整 Task Runtime

需要新加入的能力(代码已逼出来)

  • 离线可执行 Skill 与「仅在线 Mode」的明确分类
  • ASR JSON content 规范化(所有非 Chat Mode 都要)
  • 喇叭回声策略按 Mode 配置(打断 / 忽略 / 文本过滤)

7. 结论:Phase 1 是否 Done

功能 Done(带条件): 薄框架 + chat 默认 + 复读 + 翻译 + 三通道挂钩 + 离线复读,且未破坏协议语义、未改 Java DDL。

工程未 Done: 无自动化测试、无跟读采集、无播放完成事件、双表漂移风险。

因此: 不开启 Java Task 表、不开启 Assessment、不开启 App Store。
下一阶段只做一件事:把「音频任务」从 Demo 变成 可归档的跟读会话(见 roadmap.md / phase-2-plan.md)。