docs(comments): 注释中文化 · internal/services/channelbridge(纯替换)
- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态) - 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避) - 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
This commit is contained in:
1 parent
61cf80d5ee
commit
093f38d986
28 files changed
+1089
-1089
No files matched your search
@@ -1,14 +1,14 @@
|
||||
// Package channelbridge wires the registered IM channel adapters into the Cyrene
|
||||
// runtime: it connects each configured adapter, installs the inbound message
|
||||
// handler, and routes every normalized inbound message through the
|
||||
// channel→session→actor→loop→reply pipeline (DevDocs/design/08 §0-§2).
|
||||
// channelbridge 包把已注册的 IM 渠道适配器接入 Cyrene 运行时:
|
||||
// 它连接每个已配置的适配器、装配入站消息处理器,并让每条规范化后的入站消息
|
||||
// 依次流经
|
||||
// 渠道→会话→actor→循环→回复 流水线(DevDocs/design/08 §0-§2)。
|
||||
//
|
||||
// The bridge is deliberately generic over adapter.PlatformAdapter. It owns the
|
||||
// per-platform Connect lifecycle and the inbound→reply path; the concrete
|
||||
// adapters themselves (and their configuration, sourced from env) are assembled
|
||||
// in cmd/cyrene/wire.go and handed in via Add. It never reads message text to
|
||||
// decide identity — that is ResolveActor's job (actor metadata + owner whitelist,
|
||||
// a project red line).
|
||||
// 桥接器刻意对 adapter.PlatformAdapter 保持泛化。它掌管
|
||||
// 按平台的 Connect 生命周期与「入站→回复」链路;具体适配器
|
||||
// 本身(及其来自环境变量的配置)在 cmd/cyrene/wire.go 中组装,
|
||||
// 并通过 Add 注入。它绝不读取消息正文来
|
||||
// 决定身份——那是 ResolveActor 的职责(actor 元数据 + 主人白名单,
|
||||
// 即项目红线)。
|
||||
package channelbridge
|
||||
|
||||
import (
|
||||
@@ -29,57 +29,57 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/user"
|
||||
)
|
||||
|
||||
// webhookAdapter is the optional capability a channel adapter may expose to
|
||||
// source an HTTP inbound callback. *webhook.Adapter and *kook.Adapter (webhook
|
||||
// mode) implement it. The bridge reports these so the gateway can mount them.
|
||||
// webhookAdapter 是渠道适配器可选择性暴露的能力,用于
|
||||
// 提供 HTTP 入站回调。*webhook.Adapter 与 *kook.Adapter(webhook
|
||||
// 模式)实现了它。桥接器上报这些能力,好让 gateway 挂载它们。
|
||||
type webhookAdapter interface {
|
||||
// InboundHandler returns the http.Handler the gateway mounts at the route to
|
||||
// receive external callbacks.
|
||||
// InboundHandler 返回 gateway 挂载在路由上、用于
|
||||
// 接收外部回调的 http.Handler。
|
||||
InboundHandler() http.Handler
|
||||
// WebhookPath returns the route the gateway should mount the handler at. It
|
||||
// returns "" when the adapter is not currently exposing a webhook, letting the
|
||||
// bridge skip it (e.g. a websocket-mode KOOK).
|
||||
// WebhookPath 返回 gateway 应挂载该处理器的路由。适配器当前
|
||||
// 未暴露 webhook 时返回 "",让
|
||||
// 桥接器跳过它(例如 websocket 模式的 KOOK)。
|
||||
WebhookPath() string
|
||||
}
|
||||
|
||||
// Knob constants. Compile-time so no bare magic number appears in the run path
|
||||
// (project rule §10).
|
||||
// 调节参数常量。编译期常量,运行路径上就不会出现裸魔法数字
|
||||
// (项目规范 §10)。
|
||||
const (
|
||||
// connectTimeout bounds a single adapter Connect so a hung dial cannot stall
|
||||
// boot (a failed connect logs a warning and is skipped, never fatal).
|
||||
// connectTimeout 限定单次适配器 Connect 的时长,卡住的拨号不会拖住
|
||||
// 启动(连接失败只记警告并跳过,绝不致命)。
|
||||
connectTimeout = 10 * time.Second
|
||||
// maxConcurrent bounds how many inbound messages run the agent loop at once,
|
||||
// so a burst of channel messages cannot spawn unbounded goroutines.
|
||||
// maxConcurrent 限定同时运行 agent 循环的入站消息数,
|
||||
// 渠道消息突发时不会无限制地派生 goroutine。
|
||||
maxConcurrent = 16
|
||||
)
|
||||
|
||||
// ErrNilLoop is returned when the bridge is built without a chat runner.
|
||||
// ErrNilLoop 表示构建桥接器时未提供对话运行器。
|
||||
var ErrNilLoop = errors.New("channelbridge: nil loop runner")
|
||||
|
||||
// ErrNilSessions is returned when the bridge is built without a session store.
|
||||
// ErrNilSessions 表示构建桥接器时未提供会话存储。
|
||||
var ErrNilSessions = errors.New("channelbridge: nil session store")
|
||||
|
||||
// Chatter runs a single agent turn. *loop.Loop is the production implementation;
|
||||
// tests inject a stub.
|
||||
// Chatter 执行单轮 agent。生产实现是 *loop.Loop;
|
||||
// 测试注入桩实现。
|
||||
type Chatter interface {
|
||||
Run(ctx context.Context, sessionID string, messages []model.Message) (*loop.Result, error)
|
||||
}
|
||||
|
||||
// SessionStore resolves the channel↔session binding for an inbound message.
|
||||
// *session.SessionManager is the production implementation.
|
||||
// SessionStore 解析一条入站消息的「渠道↔会话」绑定。
|
||||
// 生产实现是 *session.SessionManager。
|
||||
type SessionStore interface {
|
||||
GetOrCreate(ctx context.Context, msg adapter.UnifiedMessage, owner *user.OwnerProfile) (session.Session, error)
|
||||
}
|
||||
|
||||
// Options carries the bridge's injected dependencies. Loop and Sessions are
|
||||
// required; Owner may be nil (then no account is recognized as owner, the safe
|
||||
// default-deny posture from design/11 §0). History and OnPersisted are optional
|
||||
// (design/16 §4.4): when a history store is wired, owner-private messages are
|
||||
// routed into the main web session and tagged with the channel platform, and every
|
||||
// persisted message is handed to OnPersisted so the gateway can broadcast a
|
||||
// `message_persisted` (web chat) or `channel_persisted` (IM adapter log) event;
|
||||
// when both are absent the bridge runs exactly as before (isolated sess-xxx, no
|
||||
// chat_messages writes).
|
||||
// Options 承载桥接器注入的依赖。Loop 与 Sessions 为
|
||||
// 必填;Owner 可为 nil(此时不识别任何主人账号,即 design/11 §0 的
|
||||
// 安全默认拒绝姿态)。History 与 OnPersisted 可选
|
||||
// (design/16 §4.4):接入历史存储后,主人私聊消息会
|
||||
// 被路由进主 Web 会话并打上渠道平台标签,且每条
|
||||
// 已持久化消息都会交给 OnPersisted,好让 gateway 广播
|
||||
// `message_persisted`(Web 聊天)或 `channel_persisted`(IM 适配器日志)事件;
|
||||
// 两者都缺省时,桥接器行为与以往完全一致(隔离的 sess-xxx,
|
||||
// 不写 chat_messages)。
|
||||
type Options struct {
|
||||
Log *slog.Logger
|
||||
Loop Chatter
|
||||
@@ -87,71 +87,71 @@ type Options struct {
|
||||
Owner *user.OwnerProfile
|
||||
History history.HistoryStore
|
||||
OnPersisted func(history.Message)
|
||||
// OnRejected, when non-nil, is called after a message is dropped by the
|
||||
// allow/deny black-or-white lists (design/16 §4.6 §6), so the composition root
|
||||
// can route it into an audit trail. It is invoked with the rejected message and
|
||||
// a short reason token ("group_denied", "user_not_allowed", ...). Nil = log only.
|
||||
// OnRejected 非 nil 时,会在消息被
|
||||
// 黑白名单(allow/deny,design/16 §4.6 §6)丢弃后调用,便于组合根
|
||||
// 将其写入审计轨迹。调用时传入被拒消息与
|
||||
// 简短原因标记("group_denied"、"user_not_allowed" 等)。Nil = 仅记日志。
|
||||
//
|
||||
// It is also wired as the flow gate's OnPruned audit hook (design/12 §6 策略三/四),
|
||||
// so a message dropped by the rate limiter or the review prune reaches the same
|
||||
// audit trail with reason "rate_limited", "review_pruned" or "capped".
|
||||
// 它同时被装配为 flow gate 的 OnPruned 审计钩子(design/12 §6 策略三/四),
|
||||
// 因此被限流器或审查裁剪丢弃的消息也会进入同一
|
||||
// 审计轨迹,原因为 "rate_limited"、"review_pruned" 或 "capped"。
|
||||
OnRejected func(adapter.UnifiedMessage, string)
|
||||
// Review is the optional review model (design/12 §6 策略四) that prunes low-value
|
||||
// queued messages. Nil falls back to FIFO eviction at the total cap. Use
|
||||
// NewLLMReviewer to wrap a model.Adapter into this signature.
|
||||
// Review 是可选的审查模型(design/12 §6 策略四),用于裁剪低价值
|
||||
// 队列消息。Nil 时在总量上限处回退为 FIFO 淘汰。用
|
||||
// NewLLMReviewer 把 model.Adapter 包装成该签名。
|
||||
Review adapter.Reviewer
|
||||
// Light is the optional light-response judge for non-owner group/stranger
|
||||
// messages (design/12 §6 策略二/五 + design/18 §3). When nil the bridge uses its
|
||||
// built-in rule-based judge and fails-closed to silence, so no chat message is sent
|
||||
// to an LLM just to decide whether to bother the owner. It is consulted when the
|
||||
// rule judge is inconclusive and the participation engine (design/18) has either
|
||||
// been triggered (role-name mention / probability) or is in an ACTIVE window. Use
|
||||
// NewLLMLightJudge to wrap a model.Adapter into a cost-controlled judge.
|
||||
// Light 是可选的轻量应答判定器,用于非主人的群聊/陌生人消息
|
||||
// (design/12 §6 策略二/五 + design/18 §3)。为 nil 时桥接器使用
|
||||
// 内置规则判定器并静默失败关闭,不会仅为「是否打扰主人」而把
|
||||
// 聊天消息发给 LLM。它在规则判定器得不出结论、且参与度引擎
|
||||
// (design/18)已被触发(角色名提及 / 概率)或处于 ACTIVE 窗口时
|
||||
// 被调用。用 NewLLMLightJudge 把 model.Adapter 包成
|
||||
// 受成本控制的判定器。
|
||||
Light LightJudge
|
||||
// TriggerWords are the current persona's role-name reminder words (design/18 §2).
|
||||
// A group message containing any one routes into participation PROBING even without
|
||||
// a direct @. They never imply identity (that stays structural). Empty keeps the
|
||||
// bridge on the legacy design/12 §6 light judgement.
|
||||
// TriggerWords 是当前角色的角色名提醒词(design/18 §2)。
|
||||
// 群消息只要含有其中任一个,即使没有直接 @ 也会进入参与度 PROBING。
|
||||
// 它们绝不代表身份(身份仍由结构决定)。为空则保持
|
||||
// 桥接器沿用旧的 design/12 §6 轻量判定。
|
||||
TriggerWords []string
|
||||
// PersonaPrompt is the current persona's identity/system-prompt block handed to the
|
||||
// participation judge so it can weigh the role's tone. It carries no owner secret.
|
||||
// PersonaPrompt 是交给参与度判定器的当前角色身份/系统提示词块,
|
||||
// 使其能权衡角色的语气。其中不含主人敏感信息。
|
||||
PersonaPrompt string
|
||||
// Engage is the participation-engine config (probability, activity window, cooldown,
|
||||
// context size). When its Enabled/Probability are zero AND TriggerWords is empty the
|
||||
// bridge keeps the legacy light judgement and never consults the engine. Use
|
||||
// DefaultEngageConfig for the design/18 defaults.
|
||||
// Engage 是参与度引擎配置(概率、活跃窗口、冷却、
|
||||
// 上下文大小)。当其 Enabled/Probability 为零且 TriggerWords 为空时,
|
||||
// 桥接器沿用旧的轻量判定,永不咨询该引擎。用
|
||||
// DefaultEngageConfig 取 design/18 的默认值。
|
||||
Engage EngageConfig
|
||||
// EngageFor is an optional per-conversation override (design/18 §6 per-group knobs).
|
||||
// Nil keeps the base Engage config for every conversation (P2a).
|
||||
// EngageFor 是可选的按会话覆盖(design/18 §6 的按群参数)。
|
||||
// 为 nil 时每个会话都用基准 Engage 配置(P2a)。
|
||||
EngageFor func(platform, groupID string) *EngageConfig
|
||||
// EngageRand is the injectable [0,1) source for the probability trigger. Nil uses a
|
||||
// default math/rand source; tests inject a fixed source for determinism.
|
||||
// EngageRand 是概率触发可注入的 [0,1) 随机源。为 nil 时使用
|
||||
// 默认的 math/rand 源;测试注入固定源以保证确定性。
|
||||
EngageRand func() float64
|
||||
// QuietStartHour and QuietEndHour bound the do-not-disturb window (local clock
|
||||
// hours), shared with the backthink service so 策略五 reuses the same definition.
|
||||
// Equal non-zero hours disable the window; both zero pick the backthink defaults
|
||||
// (23-07). This is the price of keeping one source of truth across services.
|
||||
// QuietStartHour 与 QuietEndHour 限定免打扰窗口(本地时钟
|
||||
// 小时),与 backthink 服务共享,使策略五沿用同一定义。
|
||||
// 两个非零且相等的时刻关闭该窗口;都为零则取 backthink 默认值
|
||||
// (23-07)。这是跨服务保持单一事实来源的代价。
|
||||
QuietStartHour int
|
||||
QuietEndHour int
|
||||
// Now is the wall-clock source for quiet-hour judgement. Nil uses time.Now,
|
||||
// which makes time-dependent behaviour injectable in tests.
|
||||
// Now 是安静时段判定所用的墙上时钟源。为 nil 时用 time.Now,
|
||||
// 使依赖时间的行为在测试中可注入。
|
||||
Now func() time.Time
|
||||
// Factory rebuilds a platform's adapters from its effective account config
|
||||
// (channels.json → env fallback). When non-nil the bridge supports hot reload;
|
||||
// nil keeps the bridge boot-only (Reload returns ErrNilFactory).
|
||||
// Factory 依据某平台生效的账号配置重建其适配器
|
||||
// (channels.json → 环境变量回退)。非 nil 时桥接器支持热重载;
|
||||
// 为 nil 时桥接器仅支持启动期装配(Reload 返回 ErrNilFactory)。
|
||||
Factory PlatformFactory
|
||||
// ChannelPersona, when non-nil, resolves the role bound to one channel account
|
||||
// (channels.json ChannelAccount.PersonaID, design/22 §2 方案 b): it is called with
|
||||
// the platform and the local account id, and returns "" when the account binds no
|
||||
// role. It is consulted once per adapter connect/reload (never per message) and
|
||||
// only for the owner's private chat, so a group/stranger conversation can never be
|
||||
// moved into a role's scope (design/22 §7-4). Nil keeps every channel on the global
|
||||
// default role.
|
||||
// ChannelPersona 非 nil 时解析某个渠道账号绑定的角色
|
||||
// (channels.json 的 ChannelAccount.PersonaID,design/22 §2 方案 b):调用时传入
|
||||
// 平台与本地账号 id,账号未绑定角色时返回 ""。
|
||||
// 它只在每次适配器连接/重载时咨询一次(绝不按消息调用),
|
||||
// 且仅针对主人的私聊,因此群聊/陌生人会话绝不会
|
||||
// 被移入某个角色的作用域(design/22 §7-4)。为 nil 时所有渠道都使用全局
|
||||
// 默认角色。
|
||||
ChannelPersona func(platform, accountID string) string
|
||||
}
|
||||
|
||||
// Bridge owns the channel adapter lifecycle and the inbound message pipeline. It
|
||||
// is safe for concurrent use.
|
||||
// Bridge 掌管渠道适配器生命周期与入站消息流水线。它
|
||||
// 可安全并发使用。
|
||||
type Bridge struct {
|
||||
log *slog.Logger
|
||||
run Chatter
|
||||
@@ -167,17 +167,17 @@ type Bridge struct {
|
||||
quietEndHour int
|
||||
now func() time.Time
|
||||
|
||||
// Participation-engine state (design/18): per-conversation in-memory state machine
|
||||
// + the persona reminder words / engage knobs that gate it, and the injectable
|
||||
// randomness source for the probability trigger.
|
||||
// 参与度引擎状态(design/18):按会话的内存状态机,
|
||||
// 加上约束它的角色提醒词 / 参与度参数,以及概率触发
|
||||
// 可注入的随机源。
|
||||
triggerWords []string
|
||||
personaPrompt string
|
||||
engage EngageConfig
|
||||
engageFor func(platform, groupID string) *EngageConfig
|
||||
engageRand func() float64
|
||||
// bubbleGapFn is the injectable delay between IM outbound bubbles (0.5-2s),
|
||||
// defaulting to the package bubbleGap. Tests inject a fixed short gap so the
|
||||
// multi-bubble delay is asserted deterministically without sleeping a real 0.5-2s.
|
||||
// bubbleGapFn 是 IM 出站气泡之间可注入的延时(0.5-2s),
|
||||
// 默认取包内的 bubbleGap。测试注入固定的短间隔,从而
|
||||
// 无需真的 sleep 0.5-2s 就能确定性地断言多气泡延时。
|
||||
bubbleGapFn func() time.Duration
|
||||
engageMu sync.Mutex
|
||||
engageGroups map[string]*engageGroupState
|
||||
@@ -189,20 +189,20 @@ type Bridge struct {
|
||||
started bool
|
||||
closed bool
|
||||
factory PlatformFactory
|
||||
// channelPersona resolves an account's channel-level role from the effective
|
||||
// config (design/22 §2 方案 b); adapterPersona caches the resolved role per
|
||||
// connected adapter so the inbound path never re-reads channels.json. Both are
|
||||
// guarded by mu.
|
||||
// channelPersona 从生效配置解析账号的渠道级角色
|
||||
// (design/22 §2 方案 b);adapterPersona 按已连接的适配器缓存解析结果,
|
||||
// 使入站路径永不重读 channels.json。二者
|
||||
// 都由 mu 保护。
|
||||
channelPersona func(platform, accountID string) string
|
||||
adapterPersona map[string]string
|
||||
// proactiveCursor is the rotation cursor for the proactive-push no-signal
|
||||
// fallback (channelbridge/proactive.go), so the default channel is not always
|
||||
// the first whitelisted owner channel. Guarded by mu.
|
||||
// proactiveCursor 是主动推送「无信号」兜底时的轮转游标
|
||||
// (channelbridge/proactive.go),使默认渠道不会总是
|
||||
// 白名单中的第一个主人渠道。由 mu 保护。
|
||||
proactiveCursor int
|
||||
|
||||
// gateMu guards the per-adapter flow gates (design/12 §6 策略三/四). They are
|
||||
// built lazily on the first inbound message per account and are independent of
|
||||
// the adapter/connected lifecycle, so the same gate survives reconnects.
|
||||
// gateMu 保护按适配器的流控闸门(design/12 §6 策略三/四)。它们
|
||||
// 按账号在首条入站消息时惰性构建,且独立于
|
||||
// 适配器/connected 生命周期,因此同一个闸门能跨重连存活。
|
||||
gateMu sync.Mutex
|
||||
gates map[string]*adapter.FlowGate
|
||||
|
||||
@@ -213,8 +213,8 @@ type Bridge struct {
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
// New builds a Bridge from opts. It returns an error when a required dependency
|
||||
// is missing so the caller fails fast rather than silently dropping messages.
|
||||
// New 依据 opts 构建 Bridge。当必需依赖缺失时返回错误,
|
||||
// 使调用方快速失败,而不是静默丢消息。
|
||||
func New(opts Options) (*Bridge, error) {
|
||||
if opts.Loop == nil {
|
||||
return nil, ErrNilLoop
|
||||
@@ -226,8 +226,8 @@ func New(opts Options) (*Bridge, error) {
|
||||
if log == nil {
|
||||
log = logger.New(logger.Options{Out: nil})
|
||||
}
|
||||
// Quiet hours default to the same window the backthink service uses, so the two
|
||||
// services never disagree about the do-not-disturb window (design/12 §6 策略五).
|
||||
// 安静时段默认取 backthink 服务使用的同一窗口,这样两个
|
||||
// 服务之间永远不会对免打扰窗口产生分歧(design/12 §6 策略五)。
|
||||
qStart, qEnd := opts.QuietStartHour, opts.QuietEndHour
|
||||
if qStart == 0 && qEnd == 0 {
|
||||
qStart, qEnd = defaultQuietStartHour, defaultQuietEndHour
|
||||
|
||||
Reference in new issue
Block a user