.agents/design/bug/stream-resume-stale-reset.md
服务崩溃或重启时,正在生成的对话可能来不及把 chatGenerateStatus 从 generating 写回 done。如果只依赖历史的 30 分钟清理,侧栏和恢复逻辑会在较长时间内继续认为该会话还在生成,用户需要等待很久才能恢复正常状态。
流恢复的 stream 模式会持续向 Redis stream 写入数据,并通过心跳维持连接。因此可以把 Redis stream 的最近活动时间作为更精确的“服务是否还在持续生成”的依据。
MongoChat.updateTime 是否超过 30 分钟,修正速度慢。generating 状态可能残留,但 Redis stream 不再有新数据或心跳。流恢复写入 Redis stream 时,同步刷新 stream:resume:active:{teamId}:{appId}:{chatId}。
activity key 记录:
updatedAt写入策略跟 stream TTL touch 绑定,不对每个 chunk 都强制写 Redis,而是按既有 touch 间隔刷新,避免额外压力。
新增 STREAM_RESUME_INACTIVE_MS = 2 * 60 * 1000。
cleanStaleGeneratingChats 先筛选生成态且 updateTime 早于 2 分钟前的会话,再读取 Redis activity:
now - updatedAt > 2min:视为 stale。这里的 2 分钟基于 stream 模式每分钟会推送心跳的前提,给一次心跳延迟留出缓冲。
当会话 updateTime 已超过 30 分钟时,直接按旧逻辑修正为 done。
兜底作用:
如果读取 Redis activity 抛错,本轮清理记录 warn,并停止依赖 Redis 的快速判定;只保留 30 分钟兜底修正。
这样 Redis 短暂不可用时,不会把真实仍在生成的会话误改成 done。
清理任务从每 5 分钟调整为每 1 分钟,锁时间同步缩短为 1 分钟。
由于候选查询先限制 generating 且 updateTime < now - 2min,再按候选逐个检查 Redis activity,频率提升主要用于缩短异常恢复时间,不是全量高频扫描。
packages/service/core/chat/resume.ts
keyOfActive。STREAM_RESUME_INACTIVE_MS。getStreamResumeActiveState 与 isStreamResumeActiveStale。packages/service/core/chat/cleanStaleGeneratingChats.ts
modifiedCount、inactiveCount、fallbackCount,便于观察修正来源。projects/app/src/service/common/system/cron.ts
projects/app/test/api/core/chat/resume.test.ts
projects/app/test/service/core/chat/cleanStaleGeneratingChats.test.ts
generating 会话被修正为 done。