Bug Description
split 部署(server↔channel 走 internal gRPC)下,当一个 session 已有一个 run 处于 waiting_decision(例如 ask_user 挂起等待作答)时,用户发来任何新消息,admission 会正确拒绝为 ErrSessionBusy(internal/agent/runtime/session/ledger/postgres.go:496-501)。按设计,channel 收到 busy 应静默延迟、等平台重投(internal/channel/inbound/channel.go:1234-1241 分支,且不该让用户看到任何错误)。
但 gRPC 边界 mapError(internal/agent/turn/grpctransport/server.go:236-255)的 sentinel 映射表只有 ErrDuplicateTurn / ErrTeamNotServed / context.Canceled / context.DeadlineExceeded 四项,漏了 ErrSessionBusy → 落入 default 分支,返回 codes.Internal。sentinel 一过 gRPC 即被销毁,channel 侧 errors.Is(startErr, turn.ErrSessionBusy) 永不匹配,busy 分支在 split 模式下完全失效,错误被原样作为聊天消息发给用户。
对照:WS/HTTP 边界对同一 sentinel 有正确映射(internal/handlers/local_channel.go:1259-1267 映射为 session_runtime.session_busy 的 run_rejected 事件),只有 gRPC turn 边界漏了。
注意:即使哨兵正确传递,现有 busy 处理假设「平台会重投」对 Telegram 长轮询也不成立——普通消息不会重投,嵌入模式下这条消息会被静默吞掉。这需要在修复时一并考虑(至少给用户一个可读提示,或将消息排队)。
Steps to Reproduce
- split 模式部署(
internal_rpc.shared_secret 非空),bot 接 Telegram。
- 让 bot 触发
ask_user(产生 pending 问题,run 进入 waiting_decision)。
- 不用按钮,直接在输入框发任意文本。
- bot 回复
Error: rpc error: code = Internal desc = internal turn operation failed。
Error Messages / Logs
# 用户可见(Telegram 聊天消息)
Error: rpc error: code = Internal desc = internal turn operation failed
# server 侧(component=turn_rpc)
level=ERROR msg="internal turn rpc failed" operation="start turn" error="turn: thread already has a run in flight: thread <id>"
# channel 侧
level=ERROR msg="start turn failed" error="rpc error: code = Internal desc = internal turn operation failed"
Version
main(2026-09-03,274128aa1 引入 turn 边界后即存在)
Channel
Telegram(所有走 gRPC turn 边界的渠道均受影响;waiting_decision 占槽期间每条新消息都会触发)
Additional Context
- 影响放大器:waiting_decision 的 run 无 ExpiresAt 且租约持续续期,会永久占住 session 唯一活跃槽;
/stop 无效(只取消 activeStreams,park 时条目已删)。一旦进入该状态,session 内后续每条消息都撞同一堵墙。用户恢复手段:Web UI 回答该问题,或 /new 开新会话。
- 修复方向:
mapError 补 ErrSessionBusy case(建议 codes.Aborted 或可识别 message),并在 client 侧 mapClientError 还原为 turn.ErrSessionBusy,对齐 WS/HTTP 边界行为。
Bug Description
split 部署(server↔channel 走 internal gRPC)下,当一个 session 已有一个 run 处于
waiting_decision(例如 ask_user 挂起等待作答)时,用户发来任何新消息,admission 会正确拒绝为ErrSessionBusy(internal/agent/runtime/session/ledger/postgres.go:496-501)。按设计,channel 收到 busy 应静默延迟、等平台重投(internal/channel/inbound/channel.go:1234-1241分支,且不该让用户看到任何错误)。但 gRPC 边界
mapError(internal/agent/turn/grpctransport/server.go:236-255)的 sentinel 映射表只有ErrDuplicateTurn/ErrTeamNotServed/context.Canceled/context.DeadlineExceeded四项,漏了ErrSessionBusy→ 落入 default 分支,返回codes.Internal。sentinel 一过 gRPC 即被销毁,channel 侧errors.Is(startErr, turn.ErrSessionBusy)永不匹配,busy 分支在 split 模式下完全失效,错误被原样作为聊天消息发给用户。对照:WS/HTTP 边界对同一 sentinel 有正确映射(
internal/handlers/local_channel.go:1259-1267映射为session_runtime.session_busy的 run_rejected 事件),只有 gRPC turn 边界漏了。注意:即使哨兵正确传递,现有 busy 处理假设「平台会重投」对 Telegram 长轮询也不成立——普通消息不会重投,嵌入模式下这条消息会被静默吞掉。这需要在修复时一并考虑(至少给用户一个可读提示,或将消息排队)。
Steps to Reproduce
internal_rpc.shared_secret非空),bot 接 Telegram。ask_user(产生 pending 问题,run 进入 waiting_decision)。Error: rpc error: code = Internal desc = internal turn operation failed。Error Messages / Logs
Version
main(2026-09-03,274128aa1 引入 turn 边界后即存在)
Channel
Telegram(所有走 gRPC turn 边界的渠道均受影响;waiting_decision 占槽期间每条新消息都会触发)
Additional Context
/stop无效(只取消activeStreams,park 时条目已删)。一旦进入该状态,session 内后续每条消息都撞同一堵墙。用户恢复手段:Web UI 回答该问题,或/new开新会话。mapError补ErrSessionBusycase(建议codes.Aborted或可识别 message),并在 client 侧mapClientError还原为turn.ErrSessionBusy,对齐 WS/HTTP 边界行为。