mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-17 04:15:05 +00:00
feat(contacts): channel contacts table, auto-collector, contacts page & managers tab redesign
- Add channel_contacts migration (000014) with UNIQUE(channel_type, sender_id) - Add ContactStore interface with UPSERT, list, count, merge operations - Add ContactCollector with 30-min TTL cache to skip redundant DB writes - Wire auto-collection into gateway consumer on every inbound message - Add GET /v1/contacts API with pagination, search, channel_type & peer_kind filters - Rename Writers tab → Managers tab (UI-only; backend routes unchanged) - Extract InlineAddForm with scoped state, debounce cleanup, aria-expanded - Add Combobox contact picker with debounced search + auto-fill - Add Contacts page with server-side pagination, filters, i18n (en/vi/zh) - Add shared ChannelContact type, sidebar nav entry, route & query keys - Fix ILIKE wildcard escape, log CountContacts errors, extract shared type
This commit is contained in:
1 parent
1ddd2e547a
commit
c8dc9917fe
32 files changed
+1204
-416
No files matched your search
+18
-2
@@ -26,7 +26,7 @@ import (
|
||||
// and routes them through the scheduler/agent loop, then publishes the response back.
|
||||
// Also handles subagent announcements: routes them through the parent agent's session
|
||||
// (matching TS subagent-announce.ts pattern) so the agent can reformulate for the user.
|
||||
func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents *agent.Router, cfg *config.Config, sched *scheduler.Scheduler, channelMgr *channels.Manager, teamStore store.TeamStore, quotaChecker *channels.QuotaChecker, delegateMgr *tools.DelegateManager, sessStore store.SessionStore, agentStore store.AgentStore) {
|
||||
func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents *agent.Router, cfg *config.Config, sched *scheduler.Scheduler, channelMgr *channels.Manager, teamStore store.TeamStore, quotaChecker *channels.QuotaChecker, delegateMgr *tools.DelegateManager, sessStore store.SessionStore, agentStore store.AgentStore, contactCollector *store.ContactCollector) {
|
||||
slog.Info("inbound message consumer started")
|
||||
|
||||
// Inbound message deduplication (matching TS src/infra/dedupe.ts + inbound-dedupe.ts).
|
||||
@@ -110,7 +110,8 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
}
|
||||
|
||||
// Persist friendly names from channel metadata into session + user profile.
|
||||
if sessionMeta := extractSessionMetadata(msg, peerKind); len(sessionMeta) > 0 {
|
||||
sessionMeta := extractSessionMetadata(msg, peerKind)
|
||||
if len(sessionMeta) > 0 {
|
||||
sessStore.SetSessionMetadata(sessionKey, sessionMeta)
|
||||
if agentStore != nil {
|
||||
if agentUUID, err := uuid.Parse(agentID); err == nil && agentUUID != uuid.Nil {
|
||||
@@ -119,6 +120,21 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-collect channel contacts for the contact selector.
|
||||
if contactCollector != nil && msg.SenderID != "" {
|
||||
senderNumericID := msg.SenderID
|
||||
if idx := strings.IndexByte(senderNumericID, '|'); idx > 0 {
|
||||
senderNumericID = senderNumericID[:idx]
|
||||
}
|
||||
channelType := channelMgr.ChannelTypeForName(msg.Channel)
|
||||
if channelType == "" {
|
||||
channelType = msg.Channel // fallback to instance name
|
||||
}
|
||||
displayName := sessionMeta["display_name"]
|
||||
username := sessionMeta["username"]
|
||||
contactCollector.EnsureContact(ctx, channelType, msg.Channel, senderNumericID, userID, displayName, username, peerKind)
|
||||
}
|
||||
|
||||
// --- Quota check ---
|
||||
if quotaChecker != nil {
|
||||
qResult := quotaChecker.Check(ctx, userID, msg.Channel, agentLoop.ProviderName())
|
||||
|
||||
@@ -52,7 +52,7 @@ func wireHTTP(stores *store.Stores, token string, msgBus *bus.MessageBus, toolsR
|
||||
}
|
||||
|
||||
if stores != nil && stores.ChannelInstances != nil {
|
||||
channelInstancesH = httpapi.NewChannelInstancesHandler(stores.ChannelInstances, stores.Agents, token, msgBus)
|
||||
channelInstancesH = httpapi.NewChannelInstancesHandler(stores.ChannelInstances, stores.Agents, stores.Contacts, token, msgBus)
|
||||
}
|
||||
|
||||
if stores != nil && stores.Providers != nil {
|
||||
|
||||
Reference in new issue
Block a user