docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
- 四组并行完成: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:
1 parent
5c2189efb0
commit
52f08dc343
22 files changed
+455
-586
No files matched your search
+35
-45
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user