fix(gateway): 收敛 sdk.Channel 与 adapter.PlatformAdapter 接口漂移,/admin/channels 恢复枚举

根因:sdk.Channel.Connect 为 2 参 (ctx, config any),adapter.PlatformAdapter.Connect 为
1 参 (ctx),Go 无重载 → 4 个渠道(qq-obv11/weixin-ilink/telegram/webhook)只实现 1 参
Connect,类型断言 cap.Value.(sdk.Channel) 恒失败 → /api/v1/admin/channels 恒返回 []。

收敛方向:sdk.Channel.Connect 改 1 参 (ctx),与 PlatformAdapter 生命周期子集一致;
4 个真实适配器自动满足新接口,adminListChannels 枚举成功。最小改动(不改渠道实现、
不引依赖)。同步修正 sdk_test/registry_test/extra_test 的 mock。
This commit is contained in:
Aska committed 2026-09-06 14:04:52 +08:00
1 parent 77f75b0d99
commit 5dae2b9741
4 files changed
+31 -20

No files matched your search

@@ -32,7 +32,7 @@ func (s regSkill) Guardrails() []sdk.Guardrail { return nil }
type regChannel struct{ name string }
func (c regChannel) Name() string { return c.name }
func (c regChannel) Connect(_ context.Context, _ any) error { return nil }
func (c regChannel) Connect(_ context.Context) error { return nil }
func (c regChannel) Disconnect(_ context.Context) error { return nil }
func (c regChannel) IsConnected() bool { return true }
func (c regChannel) HealthCheck(_ context.Context) error { return nil }
+26 -15
View File
@@ -21,7 +21,9 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
personareg "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/registry"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
)
@@ -165,20 +167,22 @@ type stubSessions struct {
func (s *stubSessions) List(ctx context.Context) ([]session.Session, error) { return s.list, s.err }
// stubPersonas mimics PersonaStore.
// stubPersonas mimics PersonaStore, returning domain characters that the gateway
// maps to PersonaSummary.
type stubPersonas struct {
list []PersonaSummary
list []personareg.Character
}
func (s *stubPersonas) List() []PersonaSummary { return s.list }
func (s *stubPersonas) List() []personareg.Character { return s.list }
// stubMemories mimics MemoryStore.
// stubMemories mimics MemoryStore, returning domain memory items that the gateway
// maps to MemorySummary.
type stubMemories struct {
list []MemorySummary
list []memory.MemoryItem
err error
}
func (s *stubMemories) List(ctx context.Context) ([]MemorySummary, error) { return s.list, s.err }
func (s *stubMemories) List(ctx context.Context) ([]memory.MemoryItem, error) { return s.list, s.err }
// stubIoT mimics IoTStore.
type stubIoT struct {
@@ -192,7 +196,7 @@ func (s *stubIoT) List(ctx context.Context) ([]iot.Device, error) { return s.lis
type stubChannel struct{}
func (stubChannel) Name() string { return "qq" }
func (stubChannel) Connect(ctx context.Context, c any) error { return nil }
func (stubChannel) Connect(ctx context.Context) error { return nil }
func (stubChannel) Disconnect(ctx context.Context) error { return nil }
func (stubChannel) IsConnected() bool { return true }
func (stubChannel) HealthCheck(ctx context.Context) error { return nil }
@@ -365,7 +369,9 @@ func TestListSessionsAdminError(t *testing.T) {
func TestListPersonasNonEmpty(t *testing.T) {
t.Parallel()
deps := Deps{Personas: &stubPersonas{list: []PersonaSummary{{ID: "p1", Name: "苏梦", Mode: "chat"}}}}
deps := Deps{Personas: &stubPersonas{list: []personareg.Character{{
ID: "p1", Name: "苏梦", PersonaID: "p1", Default: true, Modes: []string{"chat"},
}}}}
srv, ts, jwt := buildEnv(t, cfgDefault(), deps)
_ = srv
tok := signAny(t, jwt, "acct", auth.RoleOwner)
@@ -375,21 +381,25 @@ func TestListPersonasNonEmpty(t *testing.T) {
t.Fatalf("%s status = %d, want 200 (body=%s)", path, resp.StatusCode, body)
}
var items []struct {
ID string `json:"id"`
Name string `json:"name"`
ID string `json:"id"`
Name string `json:"name"`
IsDefault bool `json:"isDefault"`
Mode string `json:"mode"`
}
if err := json.Unmarshal(decodeBody(t, body)["data"], &items); err != nil {
t.Fatalf("decode %s: %v", path, err)
}
if len(items) != 1 || items[0].Name != "苏梦" {
t.Fatalf("%s items = %+v, want single 苏梦", path, items)
if len(items) != 1 || items[0].Name != "苏梦" || items[0].ID != "p1" || !items[0].IsDefault || items[0].Mode != "chat" {
t.Fatalf("%s items = %+v, want single 苏梦/p1/default/chat", path, items)
}
}
}
func TestAdminMemoriesNonEmpty(t *testing.T) {
t.Parallel()
deps := Deps{Memories: &stubMemories{list: []MemorySummary{{ID: "m1", Scope: "project", Text: "note"}}}}
deps := Deps{Memories: &stubMemories{list: []memory.MemoryItem{{
ID: "m1", PersonaID: "persona.a", Scope: memory.ScopeProject, Content: "note",
}}}}
srv, ts, jwt := buildEnv(t, cfgDefault(), deps)
_ = srv
tok := signAny(t, jwt, "acct", auth.RoleOwner)
@@ -400,12 +410,13 @@ func TestAdminMemoriesNonEmpty(t *testing.T) {
var items []struct {
ID string `json:"id"`
Scope string `json:"scope"`
Text string `json:"text"`
}
if err := json.Unmarshal(decodeBody(t, body)["data"], &items); err != nil {
t.Fatalf("decode: %v", err)
}
if len(items) != 1 || items[0].Scope != "project" {
t.Fatalf("items = %+v, want single project", items)
if len(items) != 1 || items[0].Scope != "project" || items[0].Text != "note" {
t.Fatalf("items = %+v, want single project/note", items)
}
}
+3 -3
View File
@@ -16,9 +16,9 @@ import "context"
type Channel interface {
// Name returns the platform identifier (for example "qq", "telegram").
Name() string
// Connect establishes the platform connection using config. It may be
// called more than once and must be idempotent.
Connect(ctx context.Context, config any) error
// Connect establishes the platform connection. It may be called more than
// once and must be idempotent.
Connect(ctx context.Context) error
// Disconnect tears the connection down gracefully. It returns nil when the
// adapter was already disconnected.
Disconnect(ctx context.Context) error
+1 -1
View File
@@ -31,7 +31,7 @@ func (s stubSkill) Guardrails() []Guardrail {
type stubChannel struct{ name string }
func (s stubChannel) Name() string { return s.name }
func (s stubChannel) Connect(_ context.Context, _ any) error { return nil }
func (s stubChannel) Connect(_ context.Context) error { return nil }
func (s stubChannel) Disconnect(_ context.Context) error { return nil }
func (s stubChannel) IsConnected() bool { return true }
func (s stubChannel) HealthCheck(_ context.Context) error { return nil }