# 消息防抖与恢复(v0.2.0) 防抖只用接收时间和确定性状态机,不调用 LLM 判断用户是否说完。ASR 仍只在批次封闭后按配置处理语音。 ## 默认配置 ```yaml privateQuietMs: 4000 groupQuietMs: 1500 maxWaitMs: 12000 maxBatch: 10 ``` 时长单位为毫秒。静默时间可以设为 0;最长等待必须大于 0。新配置不再使用 `mergeWindowMs`;旧字段非零时会兼容覆盖两类静默时长,建议删除旧字段或设为 0,再使用上述参数。 ## 接收与封批 1. 验证请求与来源、去重,在同一个本地事务内写入消息和缓冲记录。落盘成功才确认接收;写入失败返回 503,并回滚这条消息对缓冲成员和截止时间的修改。 2. 缓冲键包含:服务地址/Bot、来源频道、DSH Session ID、私聊/群聊及目标 ID、发送者 UID。原始消息按接收顺序追加;不同发送者、会话、频道或聊天目标绝不合并。 3. 首条消息固定最长等待起点,新消息更新末条时间并重置该键的静默截止时间。 4. 截止时间计算为 `min(lastAcceptedAt + quietMs, firstAcceptedAt + maxWaitMs)`。重复投递不修改任一时间。 5. 静默到期、达到最长等待,或达到 `maxBatch` 条数上限时,持久化一个 ready 批次。批次从此不可追加;之后同一来源的新消息进入下一批。 6. 按封批时间排队,依次准备附件、ASR,等待 DSH 空闲,再把整批交给一次 `followup`。 例如私聊在 0、1、3 秒连续发来三条消息,会在第 7 秒封批。如果持续发来消息且一直没有 4 秒静默,则第 12 秒封批;第 12 秒及之后的新消息进入下一批。群聊静默时间独立使用 1.5 秒,不会被另一个私聊的 4 秒窗口挡住。 12 秒限制的是**收集消息的等待窗口**。媒体下载、ASR 服务处理、DSH 正在处理其他渠道或本轮模型执行可能继续耗时。DSH 忙碌时缓冲计时照常进行,形成的批次会排队,边界不会因忙碌而持续变大。 ## 合并内容 每条原始消息的 ID、发送者、时间、正文和附件元数据都保留。图片与普通文件转为 DSH 持久附件;语音携带转写结果或明确错误,并保留原音频文件。已经取得的转写/错误会写入本地状态,提交前重启不会无故再次调用 ASR;临时文件清理后需要的原文件可以从固定资源接口重新下载。 DSH 收到的是一个用户消息,内部按原顺序放置多个内容块;`source.messages` 保留全部原始消息的来源信息。批次只调用一次 `followup`。既不把不同发送者拼在一起,也不把多条原消息分别启动多个 Agent Turn。 ## 持久化状态 状态文件版本为 2,同时保存原始消息、缓冲归属、首条/末条接收时间、截止时间、封批边界和 ASR 结果。状态与批次阶段关系如下: | 批次阶段 | 含义 | 重启处理 | | --- | --- | --- | | `buffering` | 等待静默或最长等待 | 保留原时间;过期立即封批 | | `ready` | 已封批,等待处理 | 按队列恢复,不能继续追加 | | `preparing` | 准备媒体/ASR,或等待 DSH 空闲 | 回到 ready,复用已持久化的 ASR 结果 | | `dispatching` | 已持久化提交标记,可能已交给 DSH | 转为 uncertain,不自动重放 | | `done` | 本轮及应发送的自动回复已完成 | 去重,避免第二次回复 | | `failed` | 提交前处理出错 | 保留原消息与安全错误码供核查 | | `uncertain` | 提交后结果不确定 | 保留供人工核对,不自动重发 | DSH 忙碌时不抢占、不 steer、不启动并发 Turn。等待空闲不占用本轮 `turnTimeoutMs`;提交前停止可以恢复,提交后超时或崩溃则保守隔离。没有把“没等到结果”当作“没提交成功”。网络、DSH 和本地文件之间没有分布式事务,因此不能保证断电/断网条件下严格 exactly-once;通过持久去重及不重放不确定批次来避免自动重复回复。 待处理消息不因历史保留时间过期而丢弃。处理完成后以完成时间开始保留去重记录;完成历史仍受 `dedupTtlMs`、`retentionMs` 和 `maxHistory` 上限约束。 ## 配置变更、升级与回滚 缓冲会保留接收时的会话、服务/Bot 和发送者归属。更换目标 Session 或服务,或收紧白名单后,不匹配的未完成批次会保持阻塞;不会把旧消息投到新会话。恢复原绑定与授权后可继续处理。每个实例仍只绑定一个固定 Session,不要用多个进程共用同一个状态文件。 v1 状态可自动升级;旧 pending 消息按升级时当前配置归属并恢复防抖,旧 processing 因无法证明是否提交过而继续隔离为 uncertain。升级前先停插件、备份状态,并保持原 Session 和服务绑定。已存在的 v2 批次保留创建时的截止参数,新配置作用于新批次。 旧 v0.1 代码不能直接读取 v2 状态。降级时先停新版,保留 v2 文件供核查;优先为旧版使用新的 statePath。如要恢复升级前备份,必须先核对期间已提交或完成的消息,避免把旧 pending 快照再次回复。通信回滚不回滚 DSH 会话历史。 ## 排查与验证 `voce_bridge_status` 保留消息状态计数,并新增 `batchCounts`、`blockedBatches`、`nextFlushAt` 和生效防抖参数。消息内容和凭据不进入普通日志。 `tests/debounce.test.ts` 用固定时间验证静默重置、最长等待、独立键、顺序和恢复;Bridge/DSH 测试验证持久 ACK、重复投递、忙碌排队、单条用户消息、提交屏障、缓存转写及恢复。另有真正启动并终止接收子进程的测试,确认磁盘缓冲恢复后只处理一次。