fix(chat): 会话已选 LLM 时不再被「全局未激活」误拦,提示按状态细分

发送消息的前置检查只看全局激活配置(chatAgentRef),而真正执行的
runConversationSync 会优先用会话所选/Agent 绑定的配置——两处解析分叉,导致
「会话已选一个有效配置、但全局没有激活配置」时,消息在预检查阶段就被 503
「LLM 未配置」拦下,根本走不到能用所选配置的执行阶段。

- 抽出 resolveChatAgent(conversation) 作为唯一解析入口(会话/Agent 绑定优先、
  全局兜底),send 预检查与后台执行都用它,消除分叉。
- 提示按实际状态细分(chatUnavailableReason):无任何配置→提示添加;有配置但
  未激活→提示激活或为本会话指定一个;而非一律「未配置」。

相邻的 StartTriggeredRun 仍用全局 gate(触发器场景,本次不动)。

验证: 新增两个测试——三态提示区分、会话所选配置在全局无激活时仍能解析出
ChatAgent(回归防护);连本地 PG 实跑通过,server 全量无回归。
This commit is contained in:
Autumn-27 committed 2026-08-24 11:55:50 -04:00
1 parent 412a993c25
commit 7299150cfa
4 files changed
+142 -9

No files matched your search

+7 -9
View File
@@ -315,9 +315,12 @@ func (s *Server) pgSendConversationMessage(w http.ResponseWriter, r *http.Reques
writeErr(w, 400, "消息不能为空")
return
}
ca := s.chatAgentRef()
if ca == nil {
writeErr(w, 503, "LLM 未配置,无法对话")
// Same resolution as the background runner: honour the agent binding / this
// conversation's chosen profile before falling back to the global config, so a
// conversation that picked a valid LLM is not rejected just because no global
// config is active.
if s.resolveChatAgent(c) == nil {
writeErr(w, 503, s.chatUnavailableReason())
return
}
@@ -390,12 +393,7 @@ func (s *Server) runConversationSync(c *db.Conversation, msg, busyKey string) {
s.chatMu.Unlock()
}()
// precedence: the conversation agent's own binding → this conversation's pin → global.
ca := s.chatAgentRef()
if eff := s.effectiveProfileForAgent(c.AgentKey, c.LLMProfileID); eff != nil {
if pa := s.chatAgentForProfile(*eff); pa != nil {
ca = pa
}
}
ca := s.resolveChatAgent(c)
if ca == nil {
return
}