feat(gateway): admin 列表端点接真实数据——persona/session/memory 补 List() 并注入 gateway.Deps
根因:gateway.Server 定义 SessionStore/PersonaStore/MemoryStore 接口但生产从未满足:
- 底层 manager 无 List() 方法;
- wireFrontend 的 gateway.Deps{} 未传 Session/Personas/Memories。
→ /api/v1/sessions|personas 与 /api/v1/admin/{sessions,personas,memories} 恒返回 []。
改动:
- persona/registry:Registry.List() []Character(并发安全、值拷贝)。
- channel/session:SessionManager.List(ctx)(查 channel_sessions 表+按 actor_role 重建)。
- runtime/memory:Store.List(ctx)(全表,复用 scanItems)。
- gateway 三 Store 接口改为返回域类型(Character/MemoryItem),映射统一放 collect* 侧。
- cmd/cyrene/wire.go:Deps 补传 Session/Personas/Memories。
- .gitignore:cyrene→/cyrene(锚定根,不再误伤 cmd/cyrene 目录)。
实机验证:/api/v1/admin/personas 返回 {id:cyrene,name:昔涟,isDefault:true}。
This commit is contained in:
1 parent
5dae2b9741
commit
04bfa2c7a7
11 files changed
+312
-18
No files matched your search
@@ -246,6 +246,21 @@ func (s *Store) ListByPersona(ctx context.Context, personaID string) ([]MemoryIt
|
||||
return scanItems(rows, "persona item")
|
||||
}
|
||||
|
||||
// List returns every memory item, newest first, across all personas. It is the
|
||||
// aggregate/admin view that enumerates all persisted memory regardless of the
|
||||
// persona-isolation boundary; callers that must respect isolation should use
|
||||
// ListByPersona or ListScope instead. The returned slice is non-nil.
|
||||
func (s *Store) List(ctx context.Context) ([]MemoryItem, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id, persona_id, role_id, project_id, scope, kind,
|
||||
content, tags, confidence, source_conversation, created_at, updated_at,
|
||||
decay_weight FROM memory_items
|
||||
ORDER BY created_at DESC`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("memory: list items: %w", err)
|
||||
}
|
||||
return scanItems(rows, "item")
|
||||
}
|
||||
|
||||
// scanItems reads every row into a MemoryItem slice, closing rows and wrapping
|
||||
// scan/iterate failures with the operation name. A non-nil, possibly empty slice
|
||||
// is always returned on success.
|
||||
|
||||
@@ -171,6 +171,45 @@ func TestStore_ListByPersona_MissingPersona_异常(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_List_正常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
s := newTestStore(t)
|
||||
|
||||
// An empty store lists nothing but stays non-nil.
|
||||
if got, err := s.List(ctx); err != nil || got == nil || len(got) != 0 {
|
||||
t.Fatalf("List() on empty store = %#v, err=%v; want empty non-nil", got, err)
|
||||
}
|
||||
|
||||
for _, item := range []MemoryItem{
|
||||
{PersonaID: "persona.a", Content: "A 的记忆", Scope: ScopePersona, Kind: KindRelationship},
|
||||
{PersonaID: "persona.b", Content: "B 的记忆", Scope: ScopePersona, Kind: KindWorldEvent},
|
||||
{PersonaID: "persona.a", Content: "项目记忆", Scope: ScopeProject, Kind: KindWorldEvent, ProjectID: "proj.1"},
|
||||
} {
|
||||
if _, err := s.Add(ctx, item); err != nil {
|
||||
t.Fatalf("Add(%s): %v", item.Content, err)
|
||||
}
|
||||
}
|
||||
|
||||
got, err := s.List(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("List = %d items, want 3", len(got))
|
||||
}
|
||||
// Newest first (created_at DESC).
|
||||
if got[0].Content != "项目记忆" {
|
||||
t.Errorf("got[0].Content = %q, want the latest added (项目记忆)", got[0].Content)
|
||||
}
|
||||
if got[2].Content != "A 的记忆" {
|
||||
t.Errorf("got[2].Content = %q, want the oldest (A 的记忆)", got[2].Content)
|
||||
}
|
||||
if got[0].Scope != ScopeProject || got[0].PersonaID != "persona.a" {
|
||||
t.Errorf("project item fields = %+v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_Delete_正常(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in new issue
Block a user