A1 情绪接线: - loop 回合边界钩子(TurnEnd/OnTurnEnd/settleTurn, Result.Mood); 每轮一次非每步 - persona/emotion: React(valence五档) + Machine.Move(BFS最短合法路径一步) + Observer(observe/decay/好感度,仅变化才emit); 好感度步长读角色配置(cyrene delta=10, 0..700) - 落库 Mood(web session_persist.res.Mood + IM channelbridge/persist; user消息不带mood); WS mood事件(BroadcastMood,仅变化); per-persona隔离; 群聊/陌生人只读不写不发 - cmd/cyrene/emotion.go 生产接线 + wire.go 注入 loop.Options.OnTurnEnd - 22 测试(状态机被loop调用/Mood非空落库/mood帧shape/只变化才发/群聊只读) + 真二进制冒烟 - Docs: api-client v0.32 §3.7 情绪事件 + 开源对照清单(companion 应用对照) - 诚实: 本机无LLM凭据→未活体验证'模型返回后完整轮次'一环 拆分 channelbridge.go 816→257(纯搬移零逻辑): connect/inbound/outbound/blacklist/helpers.go; A1 的 IM mood 调用点落在新 inbound.go, 故与本提交同批 回滚 6 个 gofmt 顺手格式化文件(capacity_loop_test/flow/source_test/findchannel_test/admin_channels_platform_test/history) 缩 diff, 含 history.go 注释被转弯引号
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package channelbridge
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
)
|
|
|
|
// This file holds the small package-level helpers shared across the bridge files:
|
|
// platform/account naming, the sender label, and generic slice readers.
|
|
|
|
// basePlatform returns the bare platform name of a (possibly multi-account)
|
|
// adapter name like "telegram:conf0" → "telegram", so the credential gate and the
|
|
// reply-routing fallback treat every account of a platform identically.
|
|
func basePlatform(name string) string {
|
|
if base, _, ok := strings.Cut(name, ":"); ok {
|
|
return base
|
|
}
|
|
return name
|
|
}
|
|
|
|
// adapterName returns the account-adapter registration name (e.g. "kook:3bef...")
|
|
// for the adapter that received a message, or "" when a is nil. The single-account
|
|
// convenience path (process) and a platform with no registered adapter can pass a nil
|
|
// adapter, so the persist hooks tolerate it and store an empty adapter_id rather than
|
|
// dereferencing a nil adapter.
|
|
func adapterName(a adapter.PlatformAdapter) string {
|
|
if a == nil {
|
|
return ""
|
|
}
|
|
return a.Name()
|
|
}
|
|
|
|
// senderDisplayName returns the human-readable sender name used to label a
|
|
// third-party message. It prefers the resolved actor's display name (which the
|
|
// channel layer derived from channel metadata + the raw SenderName), falling back
|
|
// to the message's own SenderName, so an empty SenderName always means "the
|
|
// owner/unlabeled" rather than a nickname that resolved to nothing.
|
|
func senderDisplayName(msg adapter.UnifiedMessage, actor adapter.Actor) string {
|
|
if actor.DisplayName != "" {
|
|
return actor.DisplayName
|
|
}
|
|
return msg.SenderName
|
|
}
|
|
|
|
// sliceContains reports whether s is present in list.
|
|
func sliceContains(list []string, s string) bool {
|
|
for _, v := range list {
|
|
if v == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// firstNonEmptyStr returns a when non-empty, otherwise b.
|
|
func firstNonEmptyStr(a, b string) string {
|
|
if a != "" {
|
|
return a
|
|
}
|
|
return b
|
|
}
|