package persona import ( "context" "sort" "sync" "time" ) // Memory is a zero-dependency in-process Storage for agent personas — a light // host (or tests) gets persona persistence with no DB. Mort keeps its // GORM/MySQL Storage; contrib/store adds a durable SQLite one. type Memory struct { mu sync.RWMutex agents map[string]*Agent // by ID } // NewMemory returns an empty in-memory persona Storage. func NewMemory() *Memory { return &Memory{agents: map[string]*Agent{}} } var _ Storage = (*Memory)(nil) func (m *Memory) InitializeAgentStorage(context.Context) error { return nil } func (m *Memory) SaveAgent(_ context.Context, a *Agent) error { m.mu.Lock() defer m.mu.Unlock() cp := *a m.agents[a.ID] = &cp return nil } func (m *Memory) GetAgent(_ context.Context, id string) (*Agent, error) { m.mu.RLock() defer m.mu.RUnlock() a, ok := m.agents[id] if !ok { return nil, ErrNotFound } cp := *a return &cp, nil } func (m *Memory) GetAgentByName(_ context.Context, ownerID, name string) (*Agent, error) { m.mu.RLock() defer m.mu.RUnlock() for _, a := range m.agents { if a.OwnerID == ownerID && a.Name == name { cp := *a return &cp, nil } } return nil, ErrNotFound } func (m *Memory) listWhere(keep func(*Agent) bool) []*Agent { m.mu.RLock() defer m.mu.RUnlock() out := make([]*Agent, 0, len(m.agents)) for _, a := range m.agents { if keep == nil || keep(a) { cp := *a out = append(out, &cp) } } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out } func (m *Memory) ListAgents(_ context.Context, ownerID string) ([]*Agent, error) { return m.listWhere(func(a *Agent) bool { return a.OwnerID == ownerID }), nil } func (m *Memory) ListAllAgents(context.Context) ([]*Agent, error) { return m.listWhere(nil), nil } func (m *Memory) DeleteAgent(_ context.Context, id string) error { m.mu.Lock() defer m.mu.Unlock() delete(m.agents, id) return nil } func (m *Memory) GetAgentByWebhookSecret(_ context.Context, secret string) (*Agent, error) { if secret == "" { return nil, ErrNotFound } m.mu.RLock() defer m.mu.RUnlock() for _, a := range m.agents { if a.WebhookSecret == secret { cp := *a return &cp, nil } } return nil, ErrNotFound } func (m *Memory) ListAgentsByChatbotChannelFilter(context.Context) ([]*Agent, error) { return m.listWhere(func(a *Agent) bool { return a.ChatbotChannelFilter != "" }), nil } func (m *Memory) ListScheduledAgents(_ context.Context, dueBefore time.Time) ([]*Agent, error) { return m.listWhere(func(a *Agent) bool { return a.Schedule != "" && a.NextRunAt != nil && !a.NextRunAt.After(dueBefore) }), nil } func (m *Memory) MarkAgentScheduledRun(_ context.Context, agentID string, ranAt, nextAt time.Time) error { m.mu.Lock() defer m.mu.Unlock() a, ok := m.agents[agentID] if !ok { return ErrNotFound } a.LastScheduledRunAt = &ranAt a.NextRunAt = &nextAt return nil }