feat(channel,runtime): 批次2 后端 — 消息级角色归属进上下文 + IM 私聊随主会话角色 + 渠道级绑定
按 design/22 §2/§3/§4.4/§7(3/4/5)。 - ①「消息级角色归属」进入模型上下文(批次 1 只做存储+DTO):历史消息的发送角色现在对模型可见,且**不把系统元数据塞进用户正文**(design/11 §10 红线) - ② IM owner 私聊镜像解析出的角色 = **主会话绑定的角色**(§7-3) - ③ 渠道级绑定:ChannelAccount 加 personaId(channels.json 读写/校验/脱敏沿用既有约定)+ 角色解析优先级 **会话 > 渠道 > 默认** + **群聊/陌生人强制默认角色**(§7-4);admin 渠道配置端点支持 personaId - Docs/api-client 版本 +1 - 验收:go build/vet/test 全绿 + linux/darwin 交叉编译
This commit is contained in:
1 parent
3732c538cc
commit
5969bba3bd
22 files changed
+1396
-23
No files matched your search
@@ -35,6 +35,9 @@ type AdminChannelAccount struct {
|
||||
GroupMsgPerMin int `json:"groupMsgPerMin,omitempty"`
|
||||
MsgPerMin int `json:"msgPerMin,omitempty"`
|
||||
MaxMessages int `json:"maxMessages,omitempty"`
|
||||
// PersonaID is the role this account answers as (design/22 §2 方案 b), omitted
|
||||
// when the account binds none. It is a persona id, not a secret.
|
||||
PersonaID string `json:"personaId,omitempty"`
|
||||
// BotID/BotUsername/BotNickname are the connected bot's resolved identity
|
||||
// (kook fetches it from /api/v3/user/me on connect). They come from the live
|
||||
// adapter, not the persisted channels.json config, and are non-sensitive (the
|
||||
@@ -72,6 +75,11 @@ type channelConfigRequest struct {
|
||||
GroupMsgPerMin int `json:"groupMsgPerMin"`
|
||||
MsgPerMin int `json:"msgPerMin"`
|
||||
MaxMessages int `json:"maxMessages"`
|
||||
// PersonaID is the role this account answers as (design/22 §2 方案 b). An empty
|
||||
// value leaves the account unbound (the session binding or the global default
|
||||
// applies); an unknown id is refused with 400 so a save can never bind a role
|
||||
// that cannot answer.
|
||||
PersonaID string `json:"personaId"`
|
||||
}
|
||||
|
||||
// adminSaveChannelConfig persists one account's config for a platform into
|
||||
@@ -95,6 +103,13 @@ func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "channels_unavailable", "渠道配置服务未注入"))
|
||||
return
|
||||
}
|
||||
// A channel-level role must exist in the persona library, or the account would
|
||||
// be bound to a role that cannot answer (design/22 §2 方案 b). An omitted/empty
|
||||
// personaId is always accepted: it means "no channel binding".
|
||||
if !s.requireKnownPersona(w, req.PersonaID) {
|
||||
s.auditClaims(r, "channel.save", "error")
|
||||
return
|
||||
}
|
||||
cfg := channelConfigFromRequest(req)
|
||||
// weixin-ilink may be saved with an empty config: the panel creates the account
|
||||
// first, then the user scans the QR (get_bot_qrcode) which fills bot_token/bot_id/
|
||||
@@ -116,6 +131,7 @@ func (s *Server) adminSaveChannelConfig(w http.ResponseWriter, r *http.Request)
|
||||
GroupMsgPerMin: req.GroupMsgPerMin,
|
||||
MsgPerMin: req.MsgPerMin,
|
||||
MaxMessages: req.MaxMessages,
|
||||
PersonaID: strings.TrimSpace(req.PersonaID),
|
||||
})
|
||||
if err := runtimeconfig.SaveChannels(s.channels.Path(), updated); err != nil {
|
||||
s.auditClaims(r, "channel.save", "error")
|
||||
@@ -253,6 +269,7 @@ func adminChannelAccountOf(acct config.ChannelAccount) AdminChannelAccount {
|
||||
GroupMsgPerMin: acct.GroupMsgPerMin,
|
||||
MsgPerMin: acct.MsgPerMin,
|
||||
MaxMessages: acct.MaxMessages,
|
||||
PersonaID: acct.PersonaID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user