docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、
  voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。
- 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀
  「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。
- 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、
  linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。
- 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
This commit is contained in:
AskaEth committed 2026-10-04 16:58:06 +08:00
1 parent 5c2189efb0
commit 52f08dc343
22 files changed
+455 -586

No files matched your search

+35 -45
View File
@@ -16,13 +16,12 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/user"
)
// ownerRegistrar implements gateway.OwnerRegistrar: it persists a channel account
// id into the owner whitelist (configs/users/owner.yaml) AND hot-updates the live
// owner profile the channel bridge holds, so a weixin scan-login confirmation is
// recognized as the owner immediately — no restart, no manual edit. path and the
// live profile are captured at wiring time: the boot-loaded *user.OwnerProfile is
// the same pointer the bridge resolves actors against, so RegisterAccount on it is
// visible to ResolveActor without touching the bridge.
// ownerRegistrar 实现 gateway.OwnerRegistrar:它把一个渠道账号 id 持久化进主人
// 白名单(configs/users/owner.yaml),**并**热更新渠道 bridge 持有的活主人档案,
// 因此微信扫码登录的确认会被立即认成主人——无需重启,也无需手工改配置。path 与
// 活档案都在接线时捕获:启动加载的 *user.OwnerProfile 就是 bridge 判定 actor 时
// 依据的同一个指针,所以在它上面调 RegisterAccount 对 ResolveActor 立即可见,
// 无需触碰 bridge。
type ownerRegistrar struct {
path string
owner *user.OwnerProfile
@@ -36,10 +35,9 @@ func (r *ownerRegistrar) RegisterChannelOwner(_ context.Context, platform, accou
if err := user.RegisterOwnerAccount(r.path, platform, accountID); err != nil {
return fmt.Errorf("register owner %s:%s: %w", platform, accountID, err)
}
// Hot-effect: extend the live profile the bridge already points at. If the
// profile failed to load at boot (r.owner nil) the persist above still lands
// on disk and is recognized after a restart; log so the operator knows the
// hot path was unavailable.
// 热生效:把这次登记扩到 bridge 已经指向的那份活档案上。若档案在启动时加载
// 失败(r.owner 为 nil),上面的落盘依然写进磁盘、重启后即被识别;此处打日志
// 是为了让运维知道热路径当时不可用。
if r.owner != nil {
r.owner.RegisterAccount(platform, accountID)
} else {
@@ -56,18 +54,14 @@ func (r *ownerRegistrar) RegisterChannelOwner(_ context.Context, platform, accou
// 加载失败则为 nil,gateway 会退回「真实落盘 + 提示需重启」,绝不假装热生效。
func (r *ownerRegistrar) LiveOwnerProfile() *user.OwnerProfile { return r.owner }
// wireChannels constructs the four IM channel adapters from environment
// configuration, registers them so the admin API can enumerate them, and wires
// them into the channel bridge so a configured adapter connects and routes
// inbound messages through session→actor→loop→reply. Adapters without
// credentials stay disconnected (logged, never fatal): connecting with an empty
// config would either fail or, for weixin-ilink, trigger a blocking scan-code
// login.
// wireChannels 依环境配置构造四个 IM 渠道适配器,把它们注册进能力注册表(管理
// API 据此枚举),并接进渠道 bridge——让已配置的适配器连上,把入站消息按
// session→actor→loop→reply 路由。缺凭据的适配器保持未连接(只记日志,绝不作
// 致命错误):拿空配置去连要么失败,要么(weixin-ilink)触发阻塞式扫码登录。
func (a *app) wireChannels() error {
// The channel config is runtime-editable: channels.json (when present and
// valid) is authoritative, otherwise the env channel block is the fallback
// (design/04 §3.1). The factory and the store read the same file so a panel
// save + hot-reload keeps boot and runtime consistent.
// 渠道配置可在运行时编辑:channels.json 存在且有效时以它为准,否则回落到 env
// 里的渠道块(design/04 §3.1)。工厂与配置读取读的是同一个文件,因此面板保存 +
// 热重载能让启动口径与运行口径保持一致。
channelsPath := runtimeconfig.DefaultChannelsPath(resolveDataDir(a.cfg))
factory := &channelFactory{path: channelsPath, fallback: channelAccountsFromEnv(), log: a.log}
adapters := buildChannelAdapters(factory)
@@ -91,23 +85,21 @@ func (a *app) wireChannels() error {
History: a.history,
QuietStartHour: a.cfg.Background.QuietStartHour,
QuietEndHour: a.cfg.Background.QuietEndHour,
// Optional review model prunes low-value queued messages near the per-adapter
// cap (design/12 §6 策略四). Nil adapter → the gate falls back to FIFO evict.
// 可选的复审模型在接近单适配器上限时剔除低价值排队消息
// (design/12 §6 策略四)。适配器为 nil 时,闸门回落到 FIFO 驱逐。
Review: reviewFor(a.modelAdapter, logger.WithSource(a.log, "services/channelbridge/review")),
// Light is the design/18 §3 participation judge: a cost-controlled light LLM
// decision on a triggered group/stranger message (role-name mention / probability
// / active window). Only an actual trigger feeds the group context.
// Light 是 design/18 §3 的参与判定器:对触发命中的群聊/陌生人消息做一次
// 成本受控的轻量 LLM 决策(角色名提及 / 概率 / 活跃窗口)。只有真正触发
// 才把消息喂进群聊上下文。
Light: engageLightFor(a.modelAdapter, logger.WithSource(a.log, "services/channelbridge/engage")),
TriggerWords: a.defaultTriggerWords(),
PersonaPrompt: a.defaultInstructions(),
Engage: channelbridge.DefaultEngageConfig(),
// EngageFor is left nil (P2a); per-group overrides arrive with P2c.
// Factory lets the panel save hot-replace a platform's adapters without a
// restart (design/04 §3.1).
// EngageFor 留空(P2a);按群覆盖随 P2c 到来。
// Factory 让面板保存时热替换某平台的适配器而无需重启(design/04 §3.1)。
Factory: factory,
// ChannelPersona resolves an account's channel-level role from the same
// config the factory builds from (design/22 §2 方案 b); the bridge caches it
// per connected adapter and refreshes it on every reload.
// ChannelPersona 从工厂构建所用的同一份配置里解析账号的渠道级角色
// (design/22 §2 方案 b);bridge 按已连接的适配器缓存它,并在每次重载时刷新。
ChannelPersona: factory.Persona,
})
if berr != nil {
@@ -135,10 +127,9 @@ func (a *app) wireChannels() error {
return nil
}
// runtimeChannels is the gateway-facing view of the runtime-editable channel
// config. Load re-reads channels.json (file wins → env fallback) and Reload
// delegates to the bridge's hot-replace, so the panel's save/reload path drives
// the same source the bridge boots from.
// runtimeChannels 是「可运行时编辑的渠道配置」面向 gateway 的视图。Load 重读
// channels.json(文件优先 → env 兜底),Reload 委托给 bridge 的热替换,因此面板的
// 保存/重载路径驱动的正是 bridge 启动时的同一份来源。
type runtimeChannels struct {
path string
fallback map[string][]config.ChannelAccount
@@ -183,9 +174,9 @@ func (r *runtimeChannels) Reload(platform string) error {
return r.bridge.Reload(platform)
}
// reviewFor builds the channel-bridge review model (design/12 §6 策略四), or nil
// when no model adapter is available so the gate falls back to FIFO eviction — the
// reviewer is never invoked with a nil adapter (which would panic on .Chat).
// reviewFor 构建渠道 bridge 的复审模型(design/12 §6 策略四);无模型适配器可用时
// 返回 nil,让闸门回落到 FIFO 驱逐——绝不在适配器为 nil 时调用复审器(那会在
// .Chat 上 panic)。
func reviewFor(llm model.Adapter, log *slog.Logger) adapter.Reviewer {
if llm == nil {
return nil
@@ -193,9 +184,8 @@ func reviewFor(llm model.Adapter, log *slog.Logger) adapter.Reviewer {
return channelbridge.NewLLMReviewer(llm, log)
}
// engageLightFor builds the design/18 §3 participation judge, or nil when no model
// adapter is available so the bridge's participation engine fails closed toward
// silence (a triggered message without a judge is never answered).
// engageLightFor 构建 design/18 §3 的参与判定器;无模型适配器可用时返回 nil,让
// bridge 的参与引擎朝「沉默」方向 fail-closed(触发命中但无判定器的消息绝不作答)。
func engageLightFor(llm model.Adapter, log *slog.Logger) channelbridge.LightJudge {
if llm == nil {
return nil
@@ -203,8 +193,8 @@ func engageLightFor(llm model.Adapter, log *slog.Logger) channelbridge.LightJudg
return channelbridge.NewLLMLightJudge(llm, log)
}
// defaultTriggerWords returns the default persona's role-name reminder words
// (design/18 §2) so the channel bridge knows which group messages cause a PROBING.
// defaultTriggerWords 返回默认人格的角色名提醒词(design/18 §2),让渠道 bridge
// 知道哪些群聊消息会引发 PROBING。
func (a *app) defaultTriggerWords() []string {
if c, ok := a.personas.DefaultCharacter(); ok {
if p, ok := a.personas.Persona(c.PersonaID); ok {