docs(comments): 注释中文化 · internal/runtime/model + internal/services/backthink(54 文件,纯替换)
- 只翻注释/文档字符串:`go/scanner` 剥注释后按 token 序列+literal 逐 token 比对,**54/54 文件 SAME**;
改动文件总行数与 HEAD 漂移 = 0(严格 1:1,无合并/拆行)
- 裁决编号与 design 节号逐字保留(03.4.1/03.4.2/03.4.3/14.4.1/14.4.5/07.4.2-4、design/04 §3.1/§3.2 等);
`chat_template_kwargs` / `enable_thinking` / `reasoning_effort` / `thinking:{type}` 字段名与形状说明未动
- 存疑译法(已登记):`persona` 在叙事语境用「角色」、标识符语境保留 `persona`(仓库既有模块本身混用);
`rung`→「级」;`provider` 存在「提供商/提供者」两种落笔(可按需一键统一)
- 复核:build/vet 绿 + 两包测试绿;gofmt 干净;残留英文注释 0 条
- 既有存量(未修,只报告):`model/anthropic_test.go` 612 行 >500;`applyAnthropicEvent` 115 行 >80
This commit is contained in:
1 parent
f5cf117482
commit
3dc1232bc2
54 files changed
+1923
-1923
No files matched your search
+129
-129
@@ -12,56 +12,56 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
|
||||
)
|
||||
|
||||
// Route identifies how a proactive message was actually delivered, for the
|
||||
// monitor panel and the push result record.
|
||||
// Route 标识一条主动消息实际是如何投递的,供监控面板
|
||||
// 与推送结果记录使用。
|
||||
type Route string
|
||||
|
||||
const (
|
||||
// RouteNone means no push was attempted or no target was available.
|
||||
// RouteNone 表示未尝试推送,或无可用目标。
|
||||
RouteNone Route = "none"
|
||||
// RouteRateLimited means the push was refused by the rate limiter.
|
||||
// RouteRateLimited 表示推送被限流器拒绝。
|
||||
RouteRateLimited Route = "rate_limited"
|
||||
// RouteActiveClient means the message went to an active web/native client.
|
||||
// RouteActiveClient 表示消息发往了活跃 Web/原生客户端。
|
||||
RouteActiveClient Route = "active_client"
|
||||
// RouteIMBridge means the message fell back to the IM bridge.
|
||||
// RouteIMBridge 表示消息降级到 IM 桥。
|
||||
RouteIMBridge Route = "im_bridge"
|
||||
)
|
||||
|
||||
// Channel-hint tokens recognized by the push router. They come from the
|
||||
// conclusion's [渠道:...] marker (parseProactive) and select where a proactive
|
||||
// message is delivered. An empty/unknown hint is treated as auto. A concrete
|
||||
// platform id (kook/telegram/...) is a value other than these three.
|
||||
// 推送路由器识别的渠道提示词。它们来自结论的
|
||||
// [渠道:...] 标记(parseProactive),用于选择主动消息
|
||||
// 的投递去向。为空/未知的提示按 auto 处理。具体的
|
||||
// 平台 id(kook/telegram/...)是这三个之外的值。
|
||||
const (
|
||||
channelAuto = "auto"
|
||||
channelIM = "im"
|
||||
channelWeb = "web"
|
||||
// defaultProactivePlatform is the Platform the outbound proactive message
|
||||
// carries when the conclusion names no specific channel.
|
||||
// defaultProactivePlatform 是结论未指明具体渠道时,
|
||||
// 出站主动消息携带的 Platform。
|
||||
defaultProactivePlatform = "cyrene-backthink"
|
||||
)
|
||||
|
||||
// String returns the route as a plain label.
|
||||
// String 把路由作为普通标签返回。
|
||||
func (r Route) String() string { return string(r) }
|
||||
|
||||
// PushResult is the outcome of one proactive push attempt, recorded with each
|
||||
// thinking pass (design/12 §4).
|
||||
// PushResult 是一次主动推送尝试的结果,随每轮思考
|
||||
// 一起记录(design/12 §4)。
|
||||
type PushResult struct {
|
||||
// Pushed reports whether the message was delivered.
|
||||
// Pushed 报告消息是否已投递。
|
||||
Pushed bool
|
||||
// Route is the delivery route taken.
|
||||
// Route 是实际采用的路由。
|
||||
Route Route
|
||||
// Bypassed reports whether this push was event-driven and skipped the PushGate
|
||||
// (a real event, "该发就发"). A heartbeat push is gate-governed and never
|
||||
// bypassed.
|
||||
// Bypassed 报告本次推送是否为事件驱动、跳过了 PushGate
|
||||
// (真正的事件,"该发就发")。心跳推送受门控管辖,绝不
|
||||
// 被绕过。
|
||||
Bypassed bool
|
||||
// Detail is a short human-readable note (e.g. client count, decision reason).
|
||||
// Detail 是简短可读的备注(如客户端数量、决策理由)。
|
||||
Detail string
|
||||
// Err is the underlying error text when delivery failed, or that of the
|
||||
// provider that decided not to push.
|
||||
// Err 是投递失败时的底层错误文本,或决定不推送的
|
||||
// 提供者的错误文本。
|
||||
Err string
|
||||
}
|
||||
|
||||
// String returns a concise summary for logs.
|
||||
// String 返回用于日志的简洁摘要。
|
||||
func (p PushResult) String() string {
|
||||
if p.Err != "" {
|
||||
return fmt.Sprintf("pushed=%v route=%s bypass=%v detail=%q err=%q", p.Pushed, p.Route, p.Bypassed, p.Detail, p.Err)
|
||||
@@ -69,110 +69,110 @@ func (p PushResult) String() string {
|
||||
return fmt.Sprintf("pushed=%v route=%s bypass=%v detail=%q", p.Pushed, p.Route, p.Bypassed, p.Detail)
|
||||
}
|
||||
|
||||
// ActiveClient delivers a proactive message to every currently active web/native
|
||||
// client and reports how many received it. It is the preferred push target. The
|
||||
// gateway WebSocket hub implements this in production; tests inject a mock.
|
||||
// ActiveClient 把主动消息投递给所有当前活跃的 Web/原生
|
||||
// 客户端,并报告有多少个收到。它是首选的推送目标。
|
||||
// 生产中由 gateway 的 WebSocket hub 实现;测试注入 mock。
|
||||
type ActiveClient interface {
|
||||
// SendProactive pushes msg to each active client and returns the number of
|
||||
// clients that accepted it.
|
||||
// SendProactive 把 msg 推给每个活跃客户端,并返回
|
||||
// 接受它的客户端数量。
|
||||
SendProactive(ctx context.Context, msg adapter.UnifiedMessage) (int, error)
|
||||
}
|
||||
|
||||
// IMAdapter is the IM bridge fallback: it pushes a proactive message over an IM
|
||||
// channel. Every adapter.PlatformAdapter satisfies this subset, so the service
|
||||
// depends on the narrow interface rather than the concrete adapter type.
|
||||
// IMAdapter 是 IM 桥降级:它把主动消息经 IM
|
||||
// 渠道推送。每个 adapter.PlatformAdapter 都满足这个子集,因此本服务
|
||||
// 依赖这个窄接口而非具体适配器类型。
|
||||
type IMAdapter interface {
|
||||
// SendProactive pushes msg to the owner's IM channel.
|
||||
// SendProactive 把 msg 推给主人的 IM 渠道。
|
||||
SendProactive(ctx context.Context, msg adapter.UnifiedMessage) error
|
||||
}
|
||||
|
||||
// PushGate decides whether a proactive push may proceed right now, applying the
|
||||
// rate limits of design/11 §6 plus a per-flavor limit and a post-push cooldown. It
|
||||
// is a seam over channel/session's ProactivePolicy/RateLimiter so the service can
|
||||
// be tested with a mock.
|
||||
// PushGate 判断一次主动推送此刻能否进行,套用
|
||||
// design/11 §6 的限频规则,外加按风格限制与推送后冷却。它
|
||||
// 是对 channel/session 的 ProactivePolicy/RateLimiter 的接缝,因此本服务
|
||||
// 可以用 mock 测试。
|
||||
type PushGate interface {
|
||||
// Allow reports whether a proactive push of the given kind may be sent now.
|
||||
// Allow 报告给定风格的主动推送此刻能否发送。
|
||||
Allow(ctx context.Context, kind PushKind) bool
|
||||
// RecordPush notes a successful delivery so the cooldown/per-kind windows
|
||||
// start from a real successful push, not a merely-attempted one.
|
||||
// RecordPush 记下一次成功投递,使冷却/按风格窗口
|
||||
// 从一次真正的成功推送起算,而不是仅仅尝试过。
|
||||
RecordPush(kind PushKind)
|
||||
}
|
||||
|
||||
// PushRequest carries one proactive push attempt: the outbound message plus the
|
||||
// channel target the autonomous conclusion asked for (design/20 §2 P2).
|
||||
// ChannelHint is the lowercased [渠道:...] token: ""/auto pick the default (the
|
||||
// owner mandate: ONE channel, chosen by her / her most recent platform / the
|
||||
// whitelist default); "web" targets the active client; "im" targets the IM bridge;
|
||||
// a concrete platform id (kook/telegram/weixin-ilink/...) targets the IM bridge
|
||||
// with the message tagged for that platform.
|
||||
// PushRequest 携带一次主动推送尝试:出站消息加上
|
||||
// 自主结论所要求的目标渠道(design/20 §2 P2)。
|
||||
// ChannelHint 是已转小写的 [渠道:...] 词:""/auto 选默认(
|
||||
// 主人要求:只发一个渠道,由她 / 她最近的平台 /
|
||||
// 白名单默认决定);"web" 指向活跃客户端;"im" 指向 IM 桥;
|
||||
// 具体平台 id(kook/telegram/weixin-ilink/...)指向 IM 桥
|
||||
// 并把消息标记为该平台。
|
||||
type PushRequest struct {
|
||||
// Message is the normalized outbound proactive message.
|
||||
// Message 是归一化后的出站主动消息。
|
||||
Message adapter.UnifiedMessage
|
||||
// ChannelHint is the requested target channel, "" when she did not choose one.
|
||||
// ChannelHint 是请求的目标渠道,她未选择时为 ""。
|
||||
ChannelHint string
|
||||
// TriggerKind records what woke this pass. An event-driven push bypasses the
|
||||
// PushGate (owner mandate: "该发就发"); a heartbeat stays gate-governed.
|
||||
// TriggerKind 记录唤醒本轮的原因。事件驱动的推送绕过
|
||||
// PushGate(主人要求:"该发就发");心跳保持受门控管辖。
|
||||
TriggerKind TriggerKind
|
||||
}
|
||||
|
||||
// Pusher delivers a proactive message to ONE target channel. It is bounded by the
|
||||
// rate gate and honors the channel hint the autonomous conclusion selected: "web"
|
||||
// goes to the active client, everything else goes over the IM bridge a single time,
|
||||
// where the bridge picks the one channel (her choice → her most recent platform →
|
||||
// whitelist default, WeChat included).
|
||||
// Pusher 把主动消息投递到一个目标渠道。它受
|
||||
// 频率门约束,并尊重自主结论所选的渠道提示:"web"
|
||||
// 去活跃客户端,其余都经 IM 桥走一次,
|
||||
// 由桥挑出那一个渠道(她的选择 → 她最近的平台 →
|
||||
// 白名单默认,微信含在内)。
|
||||
type Pusher interface {
|
||||
// Push delivers req to a single target channel, per the channel hint. It never
|
||||
// blocks on a slow target.
|
||||
// Push 按渠道提示把 req 投递到单个目标渠道。它绝不
|
||||
// 阻塞在慢目标上。
|
||||
Push(ctx context.Context, req PushRequest) PushResult
|
||||
}
|
||||
|
||||
// pusher is the concrete Pusher: it consults the gate, then routes the message to
|
||||
// exactly one target — the active client for a "web" hint, otherwise the IM bridge.
|
||||
// After the local delivery it mirrors the message to the optional push outlet
|
||||
// (self-hosted service → Getui fallback) without blocking that delivery.
|
||||
// pusher 是具体的 Pusher:它先咨询频率门,再把消息路由到
|
||||
// 恰好一个目标——"web" 提示走活跃客户端,否则走 IM 桥。
|
||||
// 本地投递之后,它把消息镜像到可选的推送出口
|
||||
// (自托管服务 → 个推降级),且不阻塞该投递。
|
||||
type pusher struct {
|
||||
active ActiveClient
|
||||
im IMAdapter
|
||||
gate PushGate
|
||||
exporter PushExporter
|
||||
log *slog.Logger
|
||||
// exportDone, when non-nil, receives one value after each asynchronous export
|
||||
// completes. It exists so tests can deterministically observe the hook; the
|
||||
// send is non-blocking, so a full channel never stalls the exporter.
|
||||
// exportDone 非 nil 时,会在每次异步导出完成后收到一个值。
|
||||
// 它存在是为了让测试能确定性地观测该钩子;发送是非阻塞的,
|
||||
// 所以通道满也不会卡住导出器。
|
||||
exportDone chan struct{}
|
||||
}
|
||||
|
||||
// newPusher builds a Pusher from the optional active-client sink, IM sink and
|
||||
// rate gate. Any of them may be nil; a nil target simply makes that route
|
||||
// unavailable. The push outlet is attached separately with withExporter.
|
||||
// newPusher 由可选的活跃客户端槽、IM 槽与
|
||||
// 频率门构建 Pusher。它们都可以为 nil;目标为 nil 只是让那条路由
|
||||
// 不可用。推送出口用 withExporter 单独挂上。
|
||||
func newPusher(active ActiveClient, im IMAdapter, gate PushGate, log *slog.Logger) *pusher {
|
||||
return &pusher{active: active, im: im, gate: gate, log: log}
|
||||
}
|
||||
|
||||
// withExporter attaches the push outlet hook and returns the pusher for fluent
|
||||
// use. A nil exporter leaves the hook disabled.
|
||||
// withExporter 挂上推送出口钩子并返回 pusher 以便链式
|
||||
// 调用。exporter 为 nil 则钩子保持关闭。
|
||||
func (p *pusher) withExporter(exporter PushExporter) *pusher {
|
||||
p.exporter = exporter
|
||||
return p
|
||||
}
|
||||
|
||||
// Push implements Pusher: it applies the rate gate, then routes the message to a
|
||||
// single target. A "web" hint reaches the active web/native client; every other
|
||||
// hint ("im", "auto", or a concrete platform) goes over the IM bridge once, where
|
||||
// the bridge selects one owner channel (her [渠道:...] choice, then the platform she
|
||||
// used most recently, then the whitelist default). The IM bridge is primary, so a
|
||||
// push never fans out to several channels. When no IM sink is wired the active
|
||||
// client is the honest fallback, so the owner is still reached rather than the
|
||||
// message being dropped.
|
||||
// Push 实现 Pusher:它套用频率门,然后把消息路由到
|
||||
// 单个目标。"web" 提示到达活跃 Web/原生客户端;其它任何
|
||||
// 提示("im"、"auto" 或具体平台)经 IM 桥走一次,由
|
||||
// 桥挑出一个主人渠道(她的 [渠道:...] 选择,其次是她
|
||||
// 最近用过的平台,再次是白名单默认)。IM 桥是主路径,所以一次
|
||||
// 推送绝不扇出到多个渠道。未接线 IM 槽时,活跃
|
||||
// 客户端就是诚实的降级,因此主人仍能被触达,而不是
|
||||
// 消息被丢弃。
|
||||
func (p *pusher) Push(ctx context.Context, req PushRequest) PushResult {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return PushResult{Pushed: false, Route: RouteNone, Err: err.Error()}
|
||||
}
|
||||
kind := classifyPush(req.Message.Content)
|
||||
// A real event-driven push bypasses the PushGate: it should go out as-is ("该发就发"),
|
||||
// independent of the heartbeat throttle (60min≤1 / cooldown / per-flavor window).
|
||||
// Only the periodic heartbeat stays gate-governed, so the owner is never spammed
|
||||
// by an empty reflection. Same-event dedup is applied upstream in think().
|
||||
// 真正事件驱动的推送绕过 PushGate:它应原样发出("该发就发"),
|
||||
// 不受心跳限流约束(60min≤1 / 冷却 / 按风格窗口)。
|
||||
// 只有周期性心跳保持受门控管辖,主人因此绝不会被
|
||||
// 空洞的反思刷屏。同一事件去重在 think() 上游套用。
|
||||
eventDriven := req.TriggerKind == TriggerEvent
|
||||
if p.gate != nil && !eventDriven && !p.gate.Allow(ctx, kind) {
|
||||
p.log.Warn("backthink push rate limited", "kind", kind.String())
|
||||
@@ -186,10 +186,10 @@ func (p *pusher) Push(ctx context.Context, req PushRequest) PushResult {
|
||||
if hint == channelWeb {
|
||||
res = p.pushActive(ctx, msg)
|
||||
} else {
|
||||
// "im"/"auto"/a concrete platform all travel over the IM bridge once. A
|
||||
// generic hint hands the sentinel platform so the bridge falls through to its
|
||||
// recent-active / default selection; a concrete hint is honored when the
|
||||
// channel is whitelisted and connected.
|
||||
// "im"/"auto"/具体平台都经 IM 桥走一次。
|
||||
// 泛化提示交出哨兵平台,让桥落到它的
|
||||
// 最近活跃 / 默认选择上;具体提示在渠道
|
||||
// 已加入白名单且已连接时被尊重。
|
||||
if hint == channelIM || hint == channelAuto {
|
||||
msg.Platform = defaultProactivePlatform
|
||||
} else {
|
||||
@@ -200,16 +200,16 @@ func (p *pusher) Push(ctx context.Context, req PushRequest) PushResult {
|
||||
res = p.pushActive(ctx, msg)
|
||||
}
|
||||
}
|
||||
// Only a real delivery advances the cooldown/per-kind windows (and only for a
|
||||
// gate-governed push), so an attempted-but-failed push does not suppress a later
|
||||
// genuine one, and an event push never contributes to the heartbeat throttle.
|
||||
// 只有真正的投递才推进冷却/按风格窗口(且只对
|
||||
// 受门控的推送),因此尝试过但失败的推送不会抑制之后的
|
||||
// 真实推送,而事件推送也绝不计入心跳限流。
|
||||
if res.Pushed && p.gate != nil && !eventDriven {
|
||||
p.gate.RecordPush(kind)
|
||||
}
|
||||
// D1: mirror the proactive message to the external push outlet (self-hosted
|
||||
// unified push service → Getui fallback). Fire-and-forget: a slow or failing
|
||||
// outlet never delays or breaks the local delivery above, and a rate-limited
|
||||
// push returned before reaching here.
|
||||
// D1:把主动消息镜像到外部推送出口(自托管
|
||||
// 统一推送服务 → 个推降级)。发后不管:慢或失败的
|
||||
// 出口绝不延迟或破坏上面的本地投递,而且限流被拒的
|
||||
// 推送在此之前就已返回。
|
||||
p.exportAsync(msg)
|
||||
res = annotateHint(res, req.ChannelHint)
|
||||
if eventDriven {
|
||||
@@ -219,9 +219,9 @@ func (p *pusher) Push(ctx context.Context, req PushRequest) PushResult {
|
||||
return res
|
||||
}
|
||||
|
||||
// exportAsync reports msg to the push outlet on a detached, bounded goroutine so
|
||||
// the proactive path never blocks on the outlet. A nil exporter (hook disabled)
|
||||
// or a nil outlet result is a no-op; a delivery miss or error is only logged.
|
||||
// exportAsync 在脱离的、有界的 goroutine 上把 msg 上报给推送出口,
|
||||
// 因此主动路径绝不在出口上阻塞。exporter 为 nil(钩子关闭)
|
||||
// 或出口结果为 nil 都是空操作;投递未命中或出错只记日志。
|
||||
func (p *pusher) exportAsync(msg adapter.UnifiedMessage) {
|
||||
if p.exporter == nil {
|
||||
return
|
||||
@@ -246,7 +246,7 @@ func (p *pusher) exportAsync(msg adapter.UnifiedMessage) {
|
||||
}()
|
||||
}
|
||||
|
||||
// pushActive delivers only to the active web/native client.
|
||||
// pushActive 只投递给活跃 Web/原生客户端。
|
||||
func (p *pusher) pushActive(ctx context.Context, msg adapter.UnifiedMessage) PushResult {
|
||||
if p.active != nil {
|
||||
n, err := p.active.SendProactive(ctx, msg)
|
||||
@@ -259,7 +259,7 @@ func (p *pusher) pushActive(ctx context.Context, msg adapter.UnifiedMessage) Pus
|
||||
return PushResult{Pushed: false, Route: RouteNone, Detail: "no active client"}
|
||||
}
|
||||
|
||||
// pushIM delivers only to the IM bridge.
|
||||
// pushIM 只投递给 IM 桥。
|
||||
func (p *pusher) pushIM(ctx context.Context, msg adapter.UnifiedMessage) PushResult {
|
||||
if p.im == nil {
|
||||
return PushResult{Pushed: false, Route: RouteNone, Detail: "no IM target"}
|
||||
@@ -271,9 +271,9 @@ func (p *pusher) pushIM(ctx context.Context, msg adapter.UnifiedMessage) PushRes
|
||||
return PushResult{Pushed: true, Route: RouteIMBridge, Detail: "delivered via IM bridge"}
|
||||
}
|
||||
|
||||
// normalizeChannelHint lowercases and trims a channel hint, mapping synonyms to
|
||||
// the canonical route tokens (auto/im/web); anything else is a concrete platform
|
||||
// id (kook/telegram/...). Unknown/blank maps to auto.
|
||||
// normalizeChannelHint 把渠道提示转小写并去空白,把同义词映射到
|
||||
// 规范路由词(auto/im/web);其它一切视为具体平台
|
||||
// id(kook/telegram/...)。未知/空映射为 auto。
|
||||
func normalizeChannelHint(hint string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(hint)) {
|
||||
case "", "auto":
|
||||
@@ -287,15 +287,15 @@ func normalizeChannelHint(hint string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// isSpecificChannel reports whether hint names a concrete platform rather than a
|
||||
// routing keyword (auto/im/web).
|
||||
// isSpecificChannel 报告 hint 指的是具体平台,而不是
|
||||
// 路由关键词(auto/im/web)。
|
||||
func isSpecificChannel(hint string) bool {
|
||||
return hint != channelAuto && hint != channelIM && hint != channelWeb
|
||||
}
|
||||
|
||||
// annotateHint appends the requested channel hint to the push result detail so
|
||||
// the record/panel surfaces which channel she chose, even when the route fell
|
||||
// back. Blank/auto hints add nothing.
|
||||
// annotateHint 把请求的渠道提示附加到推送结果 detail 上,
|
||||
// 好让记录/面板显示她选了哪个渠道,即便路由发生
|
||||
// 了降级。空白/auto 提示不加任何内容。
|
||||
func annotateHint(res PushResult, hint string) PushResult {
|
||||
h := strings.ToLower(strings.TrimSpace(hint))
|
||||
if h == "" || h == channelAuto {
|
||||
@@ -310,8 +310,8 @@ func annotateHint(res PushResult, hint string) PushResult {
|
||||
return res
|
||||
}
|
||||
|
||||
// annotateBypass tags an event-driven push result so the record/panel shows it
|
||||
// skipped the PushGate, distinct from a heartbeat push that was rate-limited.
|
||||
// annotateBypass 给事件驱动的推送结果打标记,使记录/面板显示它
|
||||
// 跳过了 PushGate,与因限流被拒的心跳推送区分开。
|
||||
func annotateBypass(res PushResult) PushResult {
|
||||
if res.Detail == "" {
|
||||
res.Detail = "event_bypass"
|
||||
@@ -321,10 +321,10 @@ func annotateBypass(res PushResult) PushResult {
|
||||
return res
|
||||
}
|
||||
|
||||
// rateGate is the production PushGate: a sliding-window counter over
|
||||
// session.RateLimiter (design/11 §6 anti-spam rule) combined with a post-push
|
||||
// cooldown and a per-flavor (问候/提醒/分享/关心) window so the owner is never
|
||||
// spammed with the same style (design/12 §6 策略二).
|
||||
// rateGate 是生产用的 PushGate:一个基于
|
||||
// session.RateLimiter 的滑动窗口计数器(design/11 §6 反骚扰规则),
|
||||
// 结合推送后冷却与按风格(问候/提醒/分享/关心)窗口,
|
||||
// 使主人绝不会被同一风格刷屏(design/12 §6 策略二)。
|
||||
type rateGate struct {
|
||||
log *slog.Logger
|
||||
limiter *session.RateLimiter
|
||||
@@ -337,10 +337,10 @@ type rateGate struct {
|
||||
lastKind map[PushKind]time.Time
|
||||
}
|
||||
|
||||
// newRateGate builds a gate that allows at most limit pushes within window, keeps
|
||||
// pushes cooldown apart after a success, and limits each PushKind to one push per
|
||||
// kindWindow. A non-positive limit or window yields an always-allow limiter (no
|
||||
// rate rule); a non-positive cooldown or kindWindow disables that specific guard.
|
||||
// newRateGate 构建一个门:在 window 内最多放行 limit 次推送,
|
||||
// 成功之后推送之间至少间隔 cooldown,且每种 PushKind 在
|
||||
// kindWindow 内只允许一次。非正的 limit 或 window 得到始终放行的
|
||||
// 限流器(无限频规则);非正的 cooldown 或 kindWindow 会禁用对应的那道守护。
|
||||
func newRateGate(limit int, window, cooldown, kindWindow time.Duration, log *slog.Logger) *rateGate {
|
||||
return &rateGate{
|
||||
log: log,
|
||||
@@ -352,9 +352,9 @@ func newRateGate(limit int, window, cooldown, kindWindow time.Duration, log *slo
|
||||
}
|
||||
}
|
||||
|
||||
// Allow implements PushGate. A canceled context denies (fail closed). It refuses
|
||||
// when the last successful push is inside the cooldown, or when the same flavor
|
||||
// was delivered within kindWindow, before consulting the sliding-window limiter.
|
||||
// Allow 实现 PushGate。已取消的 context 一律拒绝(fail closed)。
|
||||
// 当最近一次成功推送仍在冷却期内,或同一风格
|
||||
// 在 kindWindow 内已投递过,它就在咨询滑动窗口限流器之前拒绝。
|
||||
func (g *rateGate) Allow(ctx context.Context, kind PushKind) bool {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false
|
||||
@@ -371,8 +371,8 @@ func (g *rateGate) Allow(ctx context.Context, kind PushKind) bool {
|
||||
return g.limiter.Allow()
|
||||
}
|
||||
|
||||
// RecordPush implements PushGate: it stamps the last successful push time and the
|
||||
// per-flavor last time so the cooldown and kind window start from a real delivery.
|
||||
// RecordPush 实现 PushGate:它记下最近成功推送时间与
|
||||
// 按风格的最近时间,使冷却与风格窗口从真实投递起算。
|
||||
func (g *rateGate) RecordPush(kind PushKind) {
|
||||
now := g.now()
|
||||
g.mu.Lock()
|
||||
@@ -381,15 +381,15 @@ func (g *rateGate) RecordPush(kind PushKind) {
|
||||
g.lastKind[kind] = now
|
||||
}
|
||||
|
||||
// buildProactiveMessage assembles the normalized outbound message carrying a
|
||||
// think conclusion's clean body. It tags the platform with the requested channel
|
||||
// when the conclusion names a concrete platform, otherwise the default backthink
|
||||
// platform. The concrete active-client and IM adapters apply their own
|
||||
// routing/targeting to the owner (red line: owner only).
|
||||
// buildProactiveMessage 拼装承载思考结论文本的、
|
||||
// 归一化的出站消息。当结论指明具体平台时,它用请求的渠道
|
||||
// 给平台打标签,否则用 backthink 的默认
|
||||
// 平台。具体的活跃客户端与 IM 适配器各自对主人施加
|
||||
// 自己的路由/寻址(红线:只针对主人)。
|
||||
//
|
||||
// The body is run through stripInternalMarkers as a final guard so a structured
|
||||
// decision/channel marker that survived parsing can never reach the user-facing
|
||||
// message (only our logs/records may carry it).
|
||||
// 正文会再过一遍 stripInternalMarkers 作为最后一道守护,因此
|
||||
// 侥幸存活过解析的结构化决策/渠道标记绝不
|
||||
// 能到达面向用户的消息(只有我们的日志/记录可以携带它)。
|
||||
func (b *Backthink) buildProactiveMessage(message, channelHint string) adapter.UnifiedMessage {
|
||||
platform := defaultProactivePlatform
|
||||
if h := normalizeChannelHint(channelHint); isSpecificChannel(h) {
|
||||
|
||||
Reference in new issue
Block a user