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
+55
-72
@@ -1,18 +1,15 @@
|
||||
package main
|
||||
|
||||
// This file builds the four built-in IM channel adapters from the runtime-editable
|
||||
// configuration. The effective source is channels.json when it is present and
|
||||
// valid (design/04 §3.1); otherwise it falls back to the env channel block
|
||||
// (CHANNELS_JSON multi-account + the single-account CHANNEL_<NAME>_* vars), which
|
||||
// stays the final fallback so a bare install boots without any config file.
|
||||
// 本文件从运行时可变配置构建四个内置 IM 渠道适配器。有效来源是 channels.json
|
||||
// (存在且合法时优先,design/04 §3.1);否则回退到 env 渠道块(CHANNELS_JSON
|
||||
// 多账号 + 单账号 CHANNEL_<NAME>_* 变量),后者是最终兜底,使裸装环境在没有任何
|
||||
// 配置文件时也能启动。
|
||||
//
|
||||
// Each account becomes its own adapter instance. A single-account env adapter is
|
||||
// named after the bare platform (e.g. "kook"); a multi-account entry is named
|
||||
// "kook:confID" and wrapped in a namedAdapter that surfaces its allow/deny lists
|
||||
// through Config() for the channel bridge's black-or-white gate (design/16 §4.6
|
||||
// §5-6). The channelFactory lets the bridge hot-replace a platform's adapters when
|
||||
// the panel saves a new config (Bridge.Reload), so a token/address/rate change
|
||||
// takes effect without a restart.
|
||||
// 每个账号各自成为一个适配器实例。单账号 env 适配器以裸平台名命名(如 "kook");
|
||||
// 多账号条目命名为 "kook:confID",并包在 namedAdapter 里,通过 Config() 暴露其
|
||||
// allow/deny 名单,供渠道桥的黑/白名单闸门使用(design/16 §4.6 §5-6)。
|
||||
// channelFactory 让桥在面板保存新配置时热替换某平台的适配器(Bridge.Reload),
|
||||
// 因此 token/地址/速率变更无需重启即可生效。
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -32,11 +29,9 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/weixin_ilink"
|
||||
)
|
||||
|
||||
// buildChannelAdapters constructs every channel adapter from the effective
|
||||
// account config (channels.json → env fallback) and returns the successfully
|
||||
// built subset. Adapter construction never dials the network; actual connection
|
||||
// happens in the channel bridge. f holds the path/fallback/logger so boot and the
|
||||
// reload factory read the same source.
|
||||
// buildChannelAdapters 从有效账号配置(channels.json → env 兜底)构建全部渠道
|
||||
// 适配器,并返回构建成功的子集。适配器构建从不发起网络连接,真正的连接发生在渠道桥
|
||||
// 里。f 持有路径/兜底/logger,使启动路径与热重载工厂读取同一来源。
|
||||
func buildChannelAdapters(f *channelFactory) []adapter.PlatformAdapter {
|
||||
accounts, warn := runtimeconfig.LoadChannels(f.path, f.fallback)
|
||||
if warn != nil {
|
||||
@@ -49,17 +44,16 @@ func buildChannelAdapters(f *channelFactory) []adapter.PlatformAdapter {
|
||||
return out
|
||||
}
|
||||
|
||||
// channelFactory is the channelbridge.PlatformFactory wired into the bridge. It
|
||||
// rebuilds a platform's adapters from the CURRENT effective config (file → env),
|
||||
// so a panel save followed by Bridge.Reload(platform) picks the new config up
|
||||
// without a restart.
|
||||
// channelFactory 是接入渠道桥的 channelbridge.PlatformFactory。它从**当前**有效
|
||||
// 配置(文件 → env)重建某平台的适配器,因此面板保存后调用 Bridge.Reload(platform)
|
||||
// 即可让新配置生效,无需重启。
|
||||
type channelFactory struct {
|
||||
path string
|
||||
fallback map[string][]config.ChannelAccount
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// Build returns the adapters for a platform from its effective account config.
|
||||
// Build 返回某平台按其有效账号配置构建出的适配器。
|
||||
func (f *channelFactory) Build(platform string) ([]adapter.PlatformAdapter, error) {
|
||||
accounts, warn := runtimeconfig.LoadChannels(f.path, f.fallback)
|
||||
if warn != nil {
|
||||
@@ -68,12 +62,11 @@ func (f *channelFactory) Build(platform string) ([]adapter.PlatformAdapter, erro
|
||||
return buildPlatformAdapters(platform, accounts[platform], f.log), nil
|
||||
}
|
||||
|
||||
// Persona returns the role bound to one channel account of a platform (design/22
|
||||
// §2 方案 b). It reads the same effective config Build does — channels.json wins,
|
||||
// env is the fallback — so the panel's save plus the bridge's hot reload stay the
|
||||
// single update path, and it is called once per adapter connect rather than per
|
||||
// message. An unknown platform/account, or an account without a personaId, returns
|
||||
// "" (no channel-level binding: the session binding or the global default applies).
|
||||
// Persona 返回某平台某个渠道账号所绑定的角色(design/22 §2 方案 b)。它读取与
|
||||
// Build 相同的有效配置 —— channels.json 优先、env 兜底 —— 因此「面板保存 + 桥
|
||||
// 热重载」始终是唯一的更新路径;它每次适配器连接调用一次,而不是每条消息调用一次。
|
||||
// 平台/账号未知,或账号未配 personaId 时返回 ""(无渠道级绑定:改用会话绑定或全局
|
||||
// 默认)。
|
||||
func (f *channelFactory) Persona(platform, accountID string) string {
|
||||
accounts, warn := runtimeconfig.LoadChannels(f.path, f.fallback)
|
||||
if warn != nil {
|
||||
@@ -87,9 +80,8 @@ func (f *channelFactory) Persona(platform, accountID string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// buildPlatformAdapters builds every account of one platform into an adapter
|
||||
// instance. A failed account build is skipped with a warn (never fatal), matching
|
||||
// the "no credentials → warn and continue" contract of the bridge.
|
||||
// buildPlatformAdapters 把某平台的全部账号各构建成一个适配器实例。某个账号构建
|
||||
// 失败只记一条 WARN 并跳过(绝不致命),与桥的「没有凭据 → 告警并继续」约定一致。
|
||||
func buildPlatformAdapters(platform string, accts []config.ChannelAccount, log *slog.Logger) []adapter.PlatformAdapter {
|
||||
out := make([]adapter.PlatformAdapter, 0, len(accts))
|
||||
for _, acct := range accts {
|
||||
@@ -103,33 +95,30 @@ func buildPlatformAdapters(platform string, accts []config.ChannelAccount, log *
|
||||
return out
|
||||
}
|
||||
|
||||
// channelAccountsFromEnv synthesizes the env fallback: the single-account
|
||||
// CHANNEL_<NAME>_* adapters plus any CHANNELS_JSON multi-account entries. It is
|
||||
// the .env fallback passed to runtimeconfig.LoadChannels when channels.json is
|
||||
// absent or invalid. .env only carries the necessary connection fallback
|
||||
// (token/base_url/...); business and advanced per-account fields (receive_mode,
|
||||
// webhook path/verify_token/encrypt_key, allow/deny lists, rate limits) are
|
||||
// configured in the panel's channel management and live in channels.json, never
|
||||
// in .env.
|
||||
// channelAccountsFromEnv 合成 env 兜底:单账号 CHANNEL_<NAME>_* 适配器,加上
|
||||
// CHANNELS_JSON 里的多账号条目。它是 channels.json 缺失或非法时传给
|
||||
// runtimeconfig.LoadChannels 的 .env 兜底。.env 只承载必要的连接兜底
|
||||
// (token/base_url/...);业务与高级的按账号字段(receive_mode、webhook
|
||||
// path/verify_token/encrypt_key、allow/deny 名单、速率限制)在面板的渠道管理里
|
||||
// 配置,落在 channels.json,绝不出现在 .env。
|
||||
//
|
||||
// A token-requiring platform (kook/telegram/weixin-ilink) is synthesized only
|
||||
// when its token env var is set; with an unset token there is no account at all,
|
||||
// so an empty-token placeholder never claims the bare platform name. qq-obv11 and
|
||||
// webhook are always present (they do not need a token; the bridge's configured()
|
||||
// gate decides whether to dial) so the channel list stays stable.
|
||||
// 需要 token 的平台(kook/telegram/weixin-ilink)只有在其 token 环境变量已设置时
|
||||
// 才合成;token 未设置就完全不存在该账号,因此空 token 的占位符绝不会占用裸平台名。
|
||||
// qq-obv11 与 webhook 始终存在(它们不需要 token;由桥的 configured() 闸门决定是否
|
||||
// 拨号),所以渠道列表保持稳定。
|
||||
func channelAccountsFromEnv() map[string][]config.ChannelAccount {
|
||||
out := map[string][]config.ChannelAccount{}
|
||||
// KOOK: only the necessary connection fields. receive_mode / webhook_path /
|
||||
// webhook_verify_token / webhook_encrypt_key are business·advanced fields that
|
||||
// go to channels.json via the panel — .env does not fall back on them.
|
||||
// KOOK:只放必要的连接字段。receive_mode / webhook_path /
|
||||
// webhook_verify_token / webhook_encrypt_key 属于业务·高级字段,经由面板写入
|
||||
// channels.json —— .env 不再对它们做兜底。
|
||||
if tok := os.Getenv("CHANNEL_KOOK_TOKEN"); tok != "" {
|
||||
out["kook"] = append(out["kook"], config.ChannelAccount{Config: map[string]any{
|
||||
"bot_token": tok,
|
||||
"base_url": os.Getenv("CHANNEL_KOOK_BASE_URL"),
|
||||
}})
|
||||
}
|
||||
// Telegram: token (necessary) + optional webhook_url (connection). Business
|
||||
// tuning goes to channels.json.
|
||||
// Telegram:token(必要)+ 可选的 webhook_url(连接)。业务调优写入
|
||||
// channels.json。
|
||||
if tok := os.Getenv("CHANNEL_TELEGRAM_BOT_TOKEN"); tok != "" {
|
||||
out["telegram"] = append(out["telegram"], config.ChannelAccount{Config: map[string]any{
|
||||
"bot_token": tok,
|
||||
@@ -175,10 +164,9 @@ func channelAccountsFromEnv() map[string][]config.ChannelAccount {
|
||||
return out
|
||||
}
|
||||
|
||||
// namedAdapter wraps a platform adapter so a multi-account instance gets a unique
|
||||
// name (platform:confID) and surfaces its allow/deny lists through Config(), which
|
||||
// the channel bridge reads to enforce the platform's black-or-white lists per
|
||||
// account (design/16 §4.6 §5-6). Everything else delegates to the inner adapter.
|
||||
// namedAdapter 包装平台适配器,使多账号实例获得唯一名字(platform:confID),并
|
||||
// 通过 Config() 暴露其 allow/deny 名单 —— 渠道桥读取它来按账号执行平台的黑/白名单
|
||||
// (design/16 §4.6 §5-6)。其余方法全部委派给内层适配器。
|
||||
type namedAdapter struct {
|
||||
adapter.PlatformAdapter
|
||||
name string
|
||||
@@ -186,9 +174,8 @@ type namedAdapter struct {
|
||||
denyGroups []string
|
||||
allowUsers []string
|
||||
denyUsers []string
|
||||
// flow limits (design/12 §6 策略三/四). They are always surfaced through
|
||||
// Config() so the bridge can build a per-account ingress gate; the bridge maps
|
||||
// the zero default to the protective values and a negative value to "off".
|
||||
// 流控限制(design/12 §6 策略三/四)。它们始终经 Config() 暴露,使桥能构建按账号
|
||||
// 的入口闸门;桥把零值默认映射为保护性取值,把负值映射为「关闭」。
|
||||
groupMsgPerMin int
|
||||
msgPerMin int
|
||||
maxMessages int
|
||||
@@ -212,9 +199,8 @@ func (n *namedAdapter) Config() map[string]any {
|
||||
return out
|
||||
}
|
||||
|
||||
// InboundHandler forwards to the inner adapter when it exposes an inbound HTTP
|
||||
// callback; the bridge type-asserts the wrapper and mounts the handler at
|
||||
// WebhookPath. It returns nil for a non-webhook inner adapter.
|
||||
// InboundHandler 在内层适配器暴露入站 HTTP 回调时转发给它;桥会对包装器做类型
|
||||
// 断言,并把该 handler 挂到 WebhookPath。内层适配器不是 webhook 时返回 nil。
|
||||
func (n *namedAdapter) InboundHandler() http.Handler {
|
||||
if inner, ok := n.PlatformAdapter.(interface{ InboundHandler() http.Handler }); ok {
|
||||
return inner.InboundHandler()
|
||||
@@ -222,8 +208,8 @@ func (n *namedAdapter) InboundHandler() http.Handler {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebhookPath forwards the inner adapter's webhook route, returning "" when the
|
||||
// inner adapter does not expose one (so the bridge skips it).
|
||||
// WebhookPath 转发内层适配器的 webhook 路由;内层不暴露该路由时返回 ""(桥据此
|
||||
// 跳过它)。
|
||||
func (n *namedAdapter) WebhookPath() string {
|
||||
if inner, ok := n.PlatformAdapter.(interface{ WebhookPath() string }); ok {
|
||||
return inner.WebhookPath()
|
||||
@@ -231,9 +217,8 @@ func (n *namedAdapter) WebhookPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// buildAccountAdapter builds one account's adapter for a platform, decoding the
|
||||
// account's generic Config map into the platform's typed config and wrapping it so
|
||||
// the bridge sees a distinct account name and its allow/deny lists.
|
||||
// buildAccountAdapter 为某平台构建单个账号的适配器:把账号的通用 Config map 解码
|
||||
// 成该平台的强类型配置,并包装起来,使桥看到唯一的账号名及其 allow/deny 名单。
|
||||
func buildAccountAdapter(platform string, acct config.ChannelAccount, log *slog.Logger) (adapter.PlatformAdapter, error) {
|
||||
name := platform
|
||||
if acct.ID != "" {
|
||||
@@ -256,11 +241,10 @@ func buildAccountAdapter(platform string, acct config.ChannelAccount, log *slog.
|
||||
if err := adapter.DecodeConfig(acct.Config, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("decode weixin-ilink config: %w", err)
|
||||
}
|
||||
// A weixin-ilink account may carry an empty bot_token: it is a pending
|
||||
// scan-login account the panel drives via the login-qr/login-status admin
|
||||
// endpoints (design/08 §4). The bridge's configured() gate skips it until
|
||||
// the scan confirms and the token is persisted, so an empty token here is
|
||||
// legitimate — build the adapter (disconnected) rather than dropping it.
|
||||
// weixin-ilink 账号允许 bot_token 为空:这是一条待扫码登录的账号,面板通过
|
||||
// login-qr/login-status 管理端点驱动它(design/08 §4)。桥的 configured() 闸门
|
||||
// 会跳过它,直到扫码确认且 token 落库,因此这里空 token 是合法的 —— 应当构建
|
||||
// 该适配器(未连接),而不是丢弃它。
|
||||
w, err := weixin_ilink.New(cfg, nil, logger.WithSource(log, "plugins/channels/weixin_ilink"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build weixin-ilink: %w", err)
|
||||
@@ -303,7 +287,7 @@ func buildAccountAdapter(platform string, acct config.ChannelAccount, log *slog.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// barePlatform returns the platform name for a possibly-suffixed "platform:confID".
|
||||
// barePlatform 返回可能带后缀的 "platform:confID" 的平台名。
|
||||
func barePlatform(name string) string {
|
||||
for i := 0; i < len(name); i++ {
|
||||
if name[i] == ':' {
|
||||
@@ -313,8 +297,7 @@ func barePlatform(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// envString returns the value of key, or fallback when the variable is unset or
|
||||
// blank.
|
||||
// envString 返回 key 的值;环境变量未设置或为空白时返回 fallback。
|
||||
func envString(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
|
||||
Reference in new issue
Block a user