mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 16:10:59 +00:00
08a2d95c0c
Phase 1 (Core):
- Migration 000022: agent_heartbeats, heartbeat_run_logs, agent_config_permissions tables
- HeartbeatStore + ConfigPermissionStore interfaces with PG implementations
- HeartbeatTicker: background poll → active hours filter → queue-aware skip → run → smart suppression → deliver/log
- Heartbeat tool: status/get/set/toggle/set_checklist/get_checklist/test/logs actions
- Permission check with wildcard scope matching + TTL cache (60s)
- RPC methods: heartbeat.get/set/toggle/test/logs/checklist.get/checklist.set
- HEARTBEAT.md routed via context file interceptor (read/write for both open + predefined agents)
- Session keys: agent:{id}:heartbeat or agent:{id}💓{ts} (isolated)
- PromptMinimal for heartbeat sessions (like cron/subagent)
- Event broadcasting + cache invalidation via bus (heartbeat + config_perms)
- Gateway wiring: ticker init, event wiring, graceful shutdown
Phase 2 (Integration):
- wakeMode: CronPayload.WakeHeartbeat triggers heartbeat after cron job completes
- Queue-aware: Scheduler.HasActiveSessionsForAgent() skips busy agents
- Stagger: deterministic FNV offset spreads heartbeats across interval
- lightContext: RunRequest.LightContext skips context files, only injects checklist
- System prompt distinguishes cron (user-scheduled tasks) vs heartbeat (autonomous monitoring)
157 lines
6.4 KiB
Go
157 lines
6.4 KiB
Go
package bus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
)
|
|
|
|
// MediaFile represents an inbound media file with its MIME type.
|
|
// Used throughout the media pipeline to preserve content type from channel download to storage.
|
|
type MediaFile struct {
|
|
Path string `json:"path"`
|
|
MimeType string `json:"mime_type,omitempty"` // e.g. "application/pdf", "image/jpeg"
|
|
}
|
|
|
|
// InboundMessage represents a message received from a channel (Telegram, Discord, etc.)
|
|
type InboundMessage struct {
|
|
Channel string `json:"channel"`
|
|
SenderID string `json:"sender_id"`
|
|
ChatID string `json:"chat_id"`
|
|
Content string `json:"content"`
|
|
Media []MediaFile `json:"media,omitempty"`
|
|
SessionKey string `json:"session_key"` // deprecated: gateway builds canonical key
|
|
PeerKind string `json:"peer_kind,omitempty"` // "direct" or "group" (used for session key)
|
|
AgentID string `json:"agent_id,omitempty"` // target agent (for multi-agent routing)
|
|
UserID string `json:"user_id,omitempty"` // external user ID for per-user scoping (memory, bootstrap)
|
|
HistoryLimit int `json:"history_limit,omitempty"` // max turns to keep in context (0=unlimited, from channel config)
|
|
ToolAllow []string `json:"tool_allow,omitempty"` // per-group tool allow list (nil = no restriction)
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// OutboundMessage represents a message to be sent to a channel.
|
|
type OutboundMessage struct {
|
|
Channel string `json:"channel"`
|
|
ChatID string `json:"chat_id"`
|
|
Content string `json:"content"`
|
|
Media []MediaAttachment `json:"media,omitempty"` // optional media attachments
|
|
Metadata map[string]string `json:"metadata,omitempty"` // channel-specific metadata
|
|
}
|
|
|
|
// MediaAttachment represents a media file to be sent with a message.
|
|
type MediaAttachment struct {
|
|
URL string `json:"url"` // file path or URL
|
|
ContentType string `json:"content_type,omitempty"` // MIME type (e.g. "image/jpeg", "video/mp4")
|
|
Caption string `json:"caption,omitempty"` // optional caption for media
|
|
}
|
|
|
|
// Event represents a server-side event to broadcast to WebSocket clients.
|
|
type Event struct {
|
|
Name string `json:"name"` // event name (e.g. "agent", "chat", "health")
|
|
Payload any `json:"payload,omitempty"`
|
|
}
|
|
|
|
// Cache invalidation kind constants.
|
|
const (
|
|
CacheKindAgent = "agent"
|
|
CacheKindBootstrap = "bootstrap"
|
|
CacheKindSkills = "skills"
|
|
CacheKindCron = "cron"
|
|
CacheKindCustomTools = "custom_tools"
|
|
CacheKindChannelInstances = "channel_instances"
|
|
CacheKindBuiltinTools = "builtin_tools"
|
|
CacheKindTeam = "team"
|
|
CacheKindUserWorkspace = "user_workspace"
|
|
CacheKindGroupFileWriters = "group_file_writers"
|
|
CacheKindSkillGrants = "skill_grants"
|
|
CacheKindMCP = "mcp"
|
|
CacheKindProvider = "provider"
|
|
CacheKindAPIKeys = "api_keys"
|
|
CacheKindHeartbeat = "heartbeat"
|
|
CacheKindConfigPerms = "config_perms"
|
|
)
|
|
|
|
// Topic constants for msgBus.Subscribe() / Broadcast().
|
|
const (
|
|
TopicCacheBootstrap = "cache:bootstrap"
|
|
TopicCacheAgent = "cache:agent"
|
|
TopicCacheSkills = "cache:skills"
|
|
TopicCacheCron = "cache:cron"
|
|
TopicCacheCustomTools = "cache:custom_tools"
|
|
TopicCacheBuiltinTools = "cache:builtin_tools"
|
|
TopicCacheTeam = "cache:team"
|
|
TopicCacheUserWorkspace = "cache:user_workspace"
|
|
TopicCacheChannelInstances = "cache:channel_instances"
|
|
TopicCacheGroupFileWriters = "cache:group_file_writers"
|
|
TopicCacheSkillGrants = "cache:skill_grants"
|
|
TopicCacheMCP = "cache:mcp"
|
|
TopicCacheProvider = "cache:provider"
|
|
TopicCacheHeartbeat = "cache:heartbeat"
|
|
TopicCacheConfigPerms = "cache:config_perms"
|
|
TopicAudit = "audit"
|
|
TopicTeamTaskAudit = "team-task-audit"
|
|
TopicChannelStreaming = "channel-streaming"
|
|
TopicConfigChanged = "config:changed"
|
|
TopicPairingRevoked = "pairing:revoked"
|
|
TopicAgentStatusChanged = "agent:status_changed"
|
|
)
|
|
|
|
// EventPairingRevoked is the event name broadcast when a paired device is revoked.
|
|
const EventPairingRevoked = "pairing.revoked"
|
|
|
|
// PairingRevokedPayload identifies the revoked device.
|
|
type PairingRevokedPayload struct {
|
|
SenderID string `json:"sender_id"`
|
|
Channel string `json:"channel"`
|
|
}
|
|
|
|
// EventAgentStatusChanged is broadcast when an agent's status changes (e.g., active → inactive).
|
|
const EventAgentStatusChanged = "agent.status_changed"
|
|
|
|
// AgentStatusChangedPayload carries agent status transition info for cascade operations.
|
|
type AgentStatusChangedPayload struct {
|
|
AgentID string `json:"agent_id"`
|
|
OldStatus string `json:"old_status"`
|
|
NewStatus string `json:"new_status"`
|
|
}
|
|
|
|
// AuditEventPayload carries audit log data emitted by handlers.
|
|
// A single subscriber persists these to the activity_logs table.
|
|
type AuditEventPayload struct {
|
|
ActorType string `json:"actor_type"`
|
|
ActorID string `json:"actor_id"`
|
|
Action string `json:"action"`
|
|
EntityType string `json:"entity_type"`
|
|
EntityID string `json:"entity_id"`
|
|
IPAddress string `json:"ip_address,omitempty"`
|
|
Details json.RawMessage `json:"details,omitempty"`
|
|
}
|
|
|
|
// CacheInvalidatePayload signals cache layers to evict stale entries.
|
|
// Used with protocol.EventCacheInvalidate events.
|
|
type CacheInvalidatePayload struct {
|
|
Kind string `json:"kind"` // CacheKind* constants
|
|
Key string `json:"key"` // agent_key, agent_id, etc. Empty = invalidate all
|
|
}
|
|
|
|
// MessageHandler handles an inbound message from a specific channel.
|
|
type MessageHandler func(InboundMessage) error
|
|
|
|
// EventHandler handles a broadcast event.
|
|
type EventHandler func(Event)
|
|
|
|
// EventPublisher abstracts event broadcast + subscription.
|
|
// Used by gateway server and agents to decouple from concrete MessageBus.
|
|
type EventPublisher interface {
|
|
Subscribe(id string, handler EventHandler)
|
|
Unsubscribe(id string)
|
|
Broadcast(event Event)
|
|
}
|
|
|
|
// MessageRouter abstracts inbound/outbound message routing between channels and the agent runtime.
|
|
type MessageRouter interface {
|
|
PublishInbound(msg InboundMessage)
|
|
ConsumeInbound(ctx context.Context) (InboundMessage, bool)
|
|
PublishOutbound(msg OutboundMessage)
|
|
SubscribeOutbound(ctx context.Context) (OutboundMessage, bool)
|
|
}
|