【渠道端到端·接线(agent-72)】 - 新建 internal/services/channelbridge/:桥接服务(Start 对配置渠道 Connect(10s超时/warn)+SetMessageHandler+ReadMessages; 入站 UnifiedMessage→sessions.GetOrCreate(ResolveActor 白名单结构性识别)→loop.Run→SendMessage 回执,chat 文本)。 - cmd/cyrene/channels.go:从 CHANNEL_* env 读 4 渠道凭据(telegram/qq_obv11/weixin_ilink/webhook)。 - wire.go:app 加 channelBridge 字段,wireChannels 构造适配器+bridge.Start(),close() 收尾。 - 无真凭据降级:无 CHANNEL_* → configured()false 跳过不 dial;Connect 失败 Warn 不阻启动; webhook 仅入站(回执 ErrUnsupported 记 debug)。身份识别绝不靠提示词。收发端到端待凭据验证。 【RAG 知识库(agent-71)】 - internal/runtime/knowledge/:真实数据源(doc=资料/记忆=经历 分离 design/03 §3③)。 ListDocuments 走 projectindex.ListProjects(+ListChapters)、Search 走 projectindex.Search(标题/描述+章节 命中行+上下文)+retriever.Retrieve(配 vector 真语义,命中标 semantic:true)。 - gateway/server.go:RagStore 接口+Deps.RAG 可空+3 路由(GET /admin/rag、POST /admin/rag/query、/delete)。 - admin.go:adminListRag/adminSearchRag(空 query 400)/adminDeleteRag(写 501 not_implemented,M10)。 - projectindex/store.go:ListProjects/ListChapters。 - 前端 Rag.tsx:docs 从 GET /admin/rag 拉、检索走 POST /admin/rag/query、删除调真端点(501 显示 Notice), 上传/分块/嵌入显示 M10 Notice;api.ts/types.ts 加 adminListRag/adminSearchRag/adminDeleteRag。 【主会话收口 wire.go】projStore 提到 app struct+wireMemory 赋值;knowledge.New(...) 注入 gateway.Deps.RAG。 实机验证:admin/rag 200(documents[] 空表非壳)、channels 枚举 4 渠道、health ok。 go build/vet/test 全绿 + 前端 tsc。wire.go 567 行。 遗留(记录):① 群聊/陌生人 MemWritable 记忆隔离未接 loop(AGENTS.md 红线,待修) ② webhook InboundHandler 需 gateway 挂载 ③ RAG 语义路径 persona-scoped(project 记忆其他 persona 不浮现)。
82 lines
3.1 KiB
Go
82 lines
3.1 KiB
Go
package main
|
|
|
|
// This file builds the four built-in IM channel adapters from environment
|
|
// configuration. The config package carries no channel block, so credentials are
|
|
// read from CHANNEL_<NAME>_* env vars here and injected into each adapter's
|
|
// concrete Config. Adapters whose construction fails are skipped rather than
|
|
// aborting boot, matching the "no credentials → warn and continue" contract of
|
|
// the channel bridge.
|
|
|
|
import (
|
|
"log/slog"
|
|
"os"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/qq_obv11"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/telegram"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/webhook"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/weixin_ilink"
|
|
)
|
|
|
|
// buildChannelAdapters constructs the four channel adapters from environment
|
|
// configuration and returns the successfully built subset. Adapter construction
|
|
// never dials the network; actual connection happens in the channel bridge.
|
|
func buildChannelAdapters(log *slog.Logger) []adapter.PlatformAdapter {
|
|
qq := qq_obv11.NewAdapter(qq_obv11.Config{
|
|
Mode: envString("CHANNEL_QQ_OBV11_MODE", "server"),
|
|
ListenAddr: os.Getenv("CHANNEL_QQ_OBV11_LISTEN_ADDR"),
|
|
RemoteURL: os.Getenv("CHANNEL_QQ_OBV11_REMOTE_URL"),
|
|
AccessToken: os.Getenv("CHANNEL_QQ_OBV11_ACCESS_TOKEN"),
|
|
SelfID: os.Getenv("CHANNEL_QQ_OBV11_SELF_ID"),
|
|
Logger: log,
|
|
})
|
|
|
|
weixin, werr := weixin_ilink.New(weixin_ilink.Config{
|
|
BaseURL: os.Getenv("CHANNEL_WEIXIN_ILINK_BASE_URL"),
|
|
CDNBaseURL: os.Getenv("CHANNEL_WEIXIN_ILINK_CDN_BASE_URL"),
|
|
BotToken: os.Getenv("CHANNEL_WEIXIN_ILINK_BOT_TOKEN"),
|
|
BotID: os.Getenv("CHANNEL_WEIXIN_ILINK_BOT_ID"),
|
|
OwnerID: os.Getenv("CHANNEL_WEIXIN_ILINK_OWNER_ID"),
|
|
}, nil, log)
|
|
if werr != nil {
|
|
// A blank config defaults to a valid base URL, so this only fires on an
|
|
// explicitly invalid one; skip the channel rather than fail startup.
|
|
log.Warn("weixin-ilink adapter build skipped", "error", werr)
|
|
weixin = nil
|
|
}
|
|
|
|
tg := telegram.New(telegram.Config{
|
|
BotToken: os.Getenv("CHANNEL_TELEGRAM_BOT_TOKEN"),
|
|
WebhookURL: os.Getenv("CHANNEL_TELEGRAM_WEBHOOK_URL"),
|
|
}, telegram.WithLogger(log))
|
|
|
|
// webhook.WithLogger does not tag provenance, so wrap base with the module
|
|
// source here to satisfy the logging rule.
|
|
wh := webhook.New(
|
|
webhook.WithConfig(webhook.Config{
|
|
EndpointPath: os.Getenv("CHANNEL_WEBHOOK_ENDPOINT_PATH"),
|
|
ChannelID: os.Getenv("CHANNEL_WEBHOOK_CHANNEL_ID"),
|
|
ChannelName: os.Getenv("CHANNEL_WEBHOOK_CHANNEL_NAME"),
|
|
Secret: os.Getenv("CHANNEL_WEBHOOK_SECRET"),
|
|
SignatureHeader: os.Getenv("CHANNEL_WEBHOOK_SIGNATURE_HEADER"),
|
|
}),
|
|
webhook.WithLogger(logger.WithSource(log, "plugins/channels/webhook")),
|
|
)
|
|
|
|
out := []adapter.PlatformAdapter{qq, tg, wh}
|
|
if weixin != nil {
|
|
out = append(out, weixin)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// envString returns the value of key, or fallback when the variable is unset or
|
|
// blank.
|
|
func envString(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|