Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
799 lines
25 KiB
Go
799 lines
25 KiB
Go
package pubsub
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/libp2p/go-libp2p/core/host"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
|
)
|
|
|
|
// PubSub handles publish/subscribe messaging for Bzzz coordination and HMMM meta-discussion
|
|
type PubSub struct {
|
|
ps *pubsub.PubSub
|
|
host host.Host
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
// Topic subscriptions
|
|
bzzzTopic *pubsub.Topic
|
|
hmmmTopic *pubsub.Topic
|
|
contextTopic *pubsub.Topic
|
|
|
|
// Message subscriptions
|
|
bzzzSub *pubsub.Subscription
|
|
hmmmSub *pubsub.Subscription
|
|
contextSub *pubsub.Subscription
|
|
|
|
// Dynamic topic management
|
|
dynamicTopics map[string]*pubsub.Topic
|
|
dynamicTopicsMux sync.RWMutex
|
|
dynamicSubs map[string]*pubsub.Subscription
|
|
dynamicSubsMux sync.RWMutex
|
|
|
|
// Configuration
|
|
bzzzTopicName string
|
|
hmmmTopicName string
|
|
contextTopicName string
|
|
|
|
// External message handler for HMMM messages
|
|
HmmmMessageHandler func(msg Message, from peer.ID)
|
|
|
|
// External message handler for Context Feedback messages
|
|
ContextFeedbackHandler func(msg Message, from peer.ID)
|
|
|
|
// Hypercore-style logging
|
|
hypercoreLog HypercoreLogger
|
|
}
|
|
|
|
// HypercoreLogger interface for dependency injection
|
|
type HypercoreLogger interface {
|
|
AppendString(logType string, data map[string]interface{}) error
|
|
GetStats() map[string]interface{}
|
|
}
|
|
|
|
// MessageType represents different types of messages
|
|
type MessageType string
|
|
|
|
const (
|
|
// Bzzz coordination messages
|
|
TaskAnnouncement MessageType = "task_announcement"
|
|
TaskClaim MessageType = "task_claim"
|
|
TaskProgress MessageType = "task_progress"
|
|
TaskComplete MessageType = "task_complete"
|
|
CapabilityBcast MessageType = "capability_broadcast" // Only broadcast when capabilities change
|
|
AvailabilityBcast MessageType = "availability_broadcast" // Regular availability status
|
|
|
|
// HMMM meta-discussion messages
|
|
MetaDiscussion MessageType = "meta_discussion" // Generic type for all discussion
|
|
TaskHelpRequest MessageType = "task_help_request" // Request for assistance
|
|
TaskHelpResponse MessageType = "task_help_response" // Response to a help request
|
|
CoordinationRequest MessageType = "coordination_request" // Request for coordination
|
|
CoordinationComplete MessageType = "coordination_complete" // Coordination session completed
|
|
DependencyAlert MessageType = "dependency_alert" // Dependency detected
|
|
EscalationTrigger MessageType = "escalation_trigger" // Human escalation needed
|
|
|
|
// Role-based collaboration messages
|
|
RoleAnnouncement MessageType = "role_announcement" // Agent announces its role and capabilities
|
|
ExpertiseRequest MessageType = "expertise_request" // Request for specific expertise
|
|
ExpertiseResponse MessageType = "expertise_response" // Response offering expertise
|
|
StatusUpdate MessageType = "status_update" // Regular status updates from agents
|
|
WorkAllocation MessageType = "work_allocation" // Allocation of work to specific roles
|
|
RoleCollaboration MessageType = "role_collaboration" // Cross-role collaboration message
|
|
MentorshipRequest MessageType = "mentorship_request" // Junior role requesting mentorship
|
|
MentorshipResponse MessageType = "mentorship_response" // Senior role providing mentorship
|
|
ProjectUpdate MessageType = "project_update" // Project-level status updates
|
|
DeliverableReady MessageType = "deliverable_ready" // Notification that deliverable is complete
|
|
|
|
// RL Context Curator feedback messages
|
|
FeedbackEvent MessageType = "feedback_event" // Context feedback for RL learning
|
|
ContextRequest MessageType = "context_request" // Request context from HCFS
|
|
ContextResponse MessageType = "context_response" // Response with context data
|
|
ContextUsage MessageType = "context_usage" // Report context usage patterns
|
|
ContextRelevance MessageType = "context_relevance" // Report context relevance scoring
|
|
|
|
// SLURP event integration messages
|
|
SlurpEventGenerated MessageType = "slurp_event_generated" // HMMM consensus generated SLURP event
|
|
SlurpEventAck MessageType = "slurp_event_ack" // Acknowledgment of SLURP event receipt
|
|
SlurpContextUpdate MessageType = "slurp_context_update" // Context update from SLURP system
|
|
)
|
|
|
|
// Message represents a Bzzz/Antennae message
|
|
type Message struct {
|
|
Type MessageType `json:"type"`
|
|
From string `json:"from"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Data map[string]interface{} `json:"data"`
|
|
HopCount int `json:"hop_count,omitempty"` // For Antennae hop limiting
|
|
|
|
// Role-based collaboration fields
|
|
FromRole string `json:"from_role,omitempty"` // Role of sender
|
|
ToRoles []string `json:"to_roles,omitempty"` // Target roles
|
|
RequiredExpertise []string `json:"required_expertise,omitempty"` // Required expertise areas
|
|
ProjectID string `json:"project_id,omitempty"` // Associated project
|
|
Priority string `json:"priority,omitempty"` // Message priority (low, medium, high, urgent)
|
|
ThreadID string `json:"thread_id,omitempty"` // Conversation thread ID
|
|
}
|
|
|
|
// NewPubSub creates a new PubSub instance for Bzzz coordination and HMMM meta-discussion
|
|
func NewPubSub(ctx context.Context, h host.Host, bzzzTopic, hmmmTopic string) (*PubSub, error) {
|
|
return NewPubSubWithLogger(ctx, h, bzzzTopic, hmmmTopic, nil)
|
|
}
|
|
|
|
// NewPubSubWithLogger creates a new PubSub instance with hypercore logging
|
|
func NewPubSubWithLogger(ctx context.Context, h host.Host, bzzzTopic, hmmmTopic string, logger HypercoreLogger) (*PubSub, error) {
|
|
if bzzzTopic == "" {
|
|
bzzzTopic = "CHORUS/coordination/v1"
|
|
}
|
|
if hmmmTopic == "" {
|
|
hmmmTopic = "hmmm/meta-discussion/v1"
|
|
}
|
|
contextTopic := "CHORUS/context-feedback/v1"
|
|
|
|
pubsubCtx, cancel := context.WithCancel(ctx)
|
|
|
|
// Create gossipsub instance with message validation
|
|
ps, err := pubsub.NewGossipSub(pubsubCtx, h,
|
|
pubsub.WithMessageSigning(true),
|
|
pubsub.WithStrictSignatureVerification(true),
|
|
pubsub.WithValidateQueueSize(256),
|
|
pubsub.WithValidateThrottle(1024),
|
|
)
|
|
if err != nil {
|
|
cancel()
|
|
return nil, fmt.Errorf("failed to create gossipsub: %w", err)
|
|
}
|
|
|
|
p := &PubSub{
|
|
ps: ps,
|
|
host: h,
|
|
ctx: pubsubCtx,
|
|
cancel: cancel,
|
|
bzzzTopicName: bzzzTopic,
|
|
hmmmTopicName: hmmmTopic,
|
|
contextTopicName: contextTopic,
|
|
dynamicTopics: make(map[string]*pubsub.Topic),
|
|
dynamicSubs: make(map[string]*pubsub.Subscription),
|
|
hypercoreLog: logger,
|
|
}
|
|
|
|
// Join static topics
|
|
if err := p.joinStaticTopics(); err != nil {
|
|
cancel()
|
|
return nil, err
|
|
}
|
|
|
|
// Start message handlers
|
|
go p.handleBzzzMessages()
|
|
go p.handleHmmmMessages()
|
|
go p.handleContextFeedbackMessages()
|
|
|
|
fmt.Printf("📡 PubSub initialized - Bzzz: %s, HMMM: %s, Context: %s\n", bzzzTopic, hmmmTopic, contextTopic)
|
|
return p, nil
|
|
}
|
|
|
|
// SetHmmmMessageHandler sets the handler for incoming HMMM messages.
|
|
func (p *PubSub) SetHmmmMessageHandler(handler func(msg Message, from peer.ID)) {
|
|
p.HmmmMessageHandler = handler
|
|
}
|
|
|
|
// SetContextFeedbackHandler sets the handler for incoming context feedback messages.
|
|
func (p *PubSub) SetContextFeedbackHandler(handler func(msg Message, from peer.ID)) {
|
|
p.ContextFeedbackHandler = handler
|
|
}
|
|
|
|
// joinStaticTopics joins the main Bzzz, HMMM, and Context Feedback topics
|
|
func (p *PubSub) joinStaticTopics() error {
|
|
// Join Bzzz coordination topic
|
|
bzzzTopic, err := p.ps.Join(p.bzzzTopicName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to join Bzzz topic: %w", err)
|
|
}
|
|
p.bzzzTopic = bzzzTopic
|
|
|
|
bzzzSub, err := bzzzTopic.Subscribe()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to subscribe to Bzzz topic: %w", err)
|
|
}
|
|
p.bzzzSub = bzzzSub
|
|
|
|
// Join HMMM meta-discussion topic
|
|
hmmmTopic, err := p.ps.Join(p.hmmmTopicName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to join HMMM topic: %w", err)
|
|
}
|
|
p.hmmmTopic = hmmmTopic
|
|
|
|
hmmmSub, err := hmmmTopic.Subscribe()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to subscribe to HMMM topic: %w", err)
|
|
}
|
|
p.hmmmSub = hmmmSub
|
|
|
|
// Join Context Feedback topic
|
|
contextTopic, err := p.ps.Join(p.contextTopicName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to join Context Feedback topic: %w", err)
|
|
}
|
|
p.contextTopic = contextTopic
|
|
|
|
contextSub, err := contextTopic.Subscribe()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to subscribe to Context Feedback topic: %w", err)
|
|
}
|
|
p.contextSub = contextSub
|
|
|
|
return nil
|
|
}
|
|
|
|
// JoinDynamicTopic joins a new topic for a specific task
|
|
func (p *PubSub) JoinDynamicTopic(topicName string) error {
|
|
p.dynamicTopicsMux.Lock()
|
|
defer p.dynamicTopicsMux.Unlock()
|
|
p.dynamicSubsMux.Lock()
|
|
defer p.dynamicSubsMux.Unlock()
|
|
|
|
if _, exists := p.dynamicTopics[topicName]; exists {
|
|
return nil // Already joined
|
|
}
|
|
|
|
topic, err := p.ps.Join(topicName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to join dynamic topic %s: %w", topicName, err)
|
|
}
|
|
|
|
sub, err := topic.Subscribe()
|
|
if err != nil {
|
|
topic.Close()
|
|
return fmt.Errorf("failed to subscribe to dynamic topic %s: %w", topicName, err)
|
|
}
|
|
|
|
p.dynamicTopics[topicName] = topic
|
|
p.dynamicSubs[topicName] = sub
|
|
|
|
// Start a handler for this new subscription
|
|
go p.handleDynamicMessages(sub)
|
|
|
|
fmt.Printf("✅ Joined dynamic topic: %s\n", topicName)
|
|
return nil
|
|
}
|
|
|
|
// JoinRoleBasedTopics joins topics based on role and expertise
|
|
func (p *PubSub) JoinRoleBasedTopics(role string, expertise []string, reportsTo []string) error {
|
|
var topicsToJoin []string
|
|
|
|
// Join role-specific topic
|
|
if role != "" {
|
|
roleTopic := fmt.Sprintf("CHORUS/roles/%s/v1", strings.ToLower(strings.ReplaceAll(role, " ", "_")))
|
|
topicsToJoin = append(topicsToJoin, roleTopic)
|
|
}
|
|
|
|
// Join expertise-specific topics
|
|
for _, exp := range expertise {
|
|
expertiseTopic := fmt.Sprintf("CHORUS/expertise/%s/v1", strings.ToLower(strings.ReplaceAll(exp, " ", "_")))
|
|
topicsToJoin = append(topicsToJoin, expertiseTopic)
|
|
}
|
|
|
|
// Join reporting hierarchy topics
|
|
for _, supervisor := range reportsTo {
|
|
supervisorTopic := fmt.Sprintf("CHORUS/hierarchy/%s/v1", strings.ToLower(strings.ReplaceAll(supervisor, " ", "_")))
|
|
topicsToJoin = append(topicsToJoin, supervisorTopic)
|
|
}
|
|
|
|
// Join all identified topics
|
|
for _, topicName := range topicsToJoin {
|
|
if err := p.JoinDynamicTopic(topicName); err != nil {
|
|
fmt.Printf("⚠️ Failed to join role-based topic %s: %v\n", topicName, err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
fmt.Printf("🎯 Joined %d role-based topics for role: %s\n", len(topicsToJoin), role)
|
|
return nil
|
|
}
|
|
|
|
// JoinProjectTopic joins a project-specific topic
|
|
func (p *PubSub) JoinProjectTopic(projectID string) error {
|
|
if projectID == "" {
|
|
return fmt.Errorf("project ID cannot be empty")
|
|
}
|
|
|
|
topicName := fmt.Sprintf("CHORUS/projects/%s/coordination/v1", projectID)
|
|
return p.JoinDynamicTopic(topicName)
|
|
}
|
|
|
|
// LeaveDynamicTopic leaves a specific task topic
|
|
func (p *PubSub) LeaveDynamicTopic(topicName string) {
|
|
p.dynamicTopicsMux.Lock()
|
|
defer p.dynamicTopicsMux.Unlock()
|
|
p.dynamicSubsMux.Lock()
|
|
defer p.dynamicSubsMux.Unlock()
|
|
|
|
if sub, exists := p.dynamicSubs[topicName]; exists {
|
|
sub.Cancel()
|
|
delete(p.dynamicSubs, topicName)
|
|
}
|
|
|
|
if topic, exists := p.dynamicTopics[topicName]; exists {
|
|
topic.Close()
|
|
delete(p.dynamicTopics, topicName)
|
|
}
|
|
|
|
fmt.Printf("🗑️ Left dynamic topic: %s\n", topicName)
|
|
}
|
|
|
|
// PublishToDynamicTopic publishes a message to a specific dynamic topic
|
|
func (p *PubSub) PublishToDynamicTopic(topicName string, msgType MessageType, data map[string]interface{}) error {
|
|
p.dynamicTopicsMux.RLock()
|
|
topic, exists := p.dynamicTopics[topicName]
|
|
p.dynamicTopicsMux.RUnlock()
|
|
|
|
if !exists {
|
|
return fmt.Errorf("not subscribed to dynamic topic: %s", topicName)
|
|
}
|
|
|
|
msg := Message{
|
|
Type: msgType,
|
|
From: p.host.ID().String(),
|
|
Timestamp: time.Now(),
|
|
Data: data,
|
|
}
|
|
|
|
msgBytes, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal message for dynamic topic: %w", err)
|
|
}
|
|
|
|
return topic.Publish(p.ctx, msgBytes)
|
|
}
|
|
|
|
// PublishRaw publishes a raw JSON payload to the given topic name without
|
|
// wrapping it in the CHORUS Message envelope. Intended for HMMM per-issue rooms
|
|
// or other modules that maintain their own schemas.
|
|
func (p *PubSub) PublishRaw(topicName string, payload []byte) error {
|
|
// Dynamic topic
|
|
p.dynamicTopicsMux.RLock()
|
|
if topic, exists := p.dynamicTopics[topicName]; exists {
|
|
p.dynamicTopicsMux.RUnlock()
|
|
return topic.Publish(p.ctx, payload)
|
|
}
|
|
p.dynamicTopicsMux.RUnlock()
|
|
|
|
// Static topics by name
|
|
switch topicName {
|
|
case p.bzzzTopicName:
|
|
return p.bzzzTopic.Publish(p.ctx, payload)
|
|
case p.hmmmTopicName:
|
|
return p.hmmmTopic.Publish(p.ctx, payload)
|
|
case p.contextTopicName:
|
|
return p.contextTopic.Publish(p.ctx, payload)
|
|
default:
|
|
return fmt.Errorf("not subscribed to topic: %s", topicName)
|
|
}
|
|
}
|
|
|
|
// PublishBzzzMessage publishes a message to the Bzzz coordination topic
|
|
func (p *PubSub) PublishBzzzMessage(msgType MessageType, data map[string]interface{}) error {
|
|
msg := Message{
|
|
Type: msgType,
|
|
From: p.host.ID().String(),
|
|
Timestamp: time.Now(),
|
|
Data: data,
|
|
}
|
|
|
|
msgBytes, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal message: %w", err)
|
|
}
|
|
|
|
return p.bzzzTopic.Publish(p.ctx, msgBytes)
|
|
}
|
|
|
|
// PublishHmmmMessage publishes a message to the HMMM meta-discussion topic
|
|
func (p *PubSub) PublishHmmmMessage(msgType MessageType, data map[string]interface{}) error {
|
|
msg := Message{
|
|
Type: msgType,
|
|
From: p.host.ID().String(),
|
|
Timestamp: time.Now(),
|
|
Data: data,
|
|
}
|
|
|
|
msgBytes, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal message: %w", err)
|
|
}
|
|
|
|
return p.hmmmTopic.Publish(p.ctx, msgBytes)
|
|
}
|
|
|
|
// PublishAntennaeMessage is a compatibility alias for PublishHmmmMessage
|
|
// Deprecated: Use PublishHmmmMessage instead
|
|
func (p *PubSub) PublishAntennaeMessage(msgType MessageType, data map[string]interface{}) error {
|
|
return p.PublishHmmmMessage(msgType, data)
|
|
}
|
|
|
|
// SetAntennaeMessageHandler is a compatibility alias for SetHmmmMessageHandler
|
|
// Deprecated: Use SetHmmmMessageHandler instead
|
|
func (p *PubSub) SetAntennaeMessageHandler(handler func(msg Message, from peer.ID)) {
|
|
p.SetHmmmMessageHandler(handler)
|
|
}
|
|
|
|
// PublishContextFeedbackMessage publishes a message to the Context Feedback topic
|
|
func (p *PubSub) PublishContextFeedbackMessage(msgType MessageType, data map[string]interface{}) error {
|
|
msg := Message{
|
|
Type: msgType,
|
|
From: p.host.ID().String(),
|
|
Timestamp: time.Now(),
|
|
Data: data,
|
|
}
|
|
|
|
msgBytes, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal context feedback message: %w", err)
|
|
}
|
|
|
|
return p.contextTopic.Publish(p.ctx, msgBytes)
|
|
}
|
|
|
|
// PublishRoleBasedMessage publishes a role-based collaboration message
|
|
func (p *PubSub) PublishRoleBasedMessage(msgType MessageType, data map[string]interface{}, opts MessageOptions) error {
|
|
msg := Message{
|
|
Type: msgType,
|
|
From: p.host.ID().String(),
|
|
Timestamp: time.Now(),
|
|
Data: data,
|
|
FromRole: opts.FromRole,
|
|
ToRoles: opts.ToRoles,
|
|
RequiredExpertise: opts.RequiredExpertise,
|
|
ProjectID: opts.ProjectID,
|
|
Priority: opts.Priority,
|
|
ThreadID: opts.ThreadID,
|
|
}
|
|
|
|
msgBytes, err := json.Marshal(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal role-based message: %w", err)
|
|
}
|
|
|
|
// Determine which topic to use based on message type
|
|
var topic *pubsub.Topic
|
|
switch msgType {
|
|
case RoleAnnouncement, ExpertiseRequest, ExpertiseResponse, StatusUpdate,
|
|
WorkAllocation, RoleCollaboration, MentorshipRequest, MentorshipResponse,
|
|
ProjectUpdate, DeliverableReady:
|
|
topic = p.hmmmTopic // Use HMMM topic for role-based messages
|
|
default:
|
|
topic = p.bzzzTopic // Default to Bzzz topic
|
|
}
|
|
|
|
return topic.Publish(p.ctx, msgBytes)
|
|
}
|
|
|
|
// PublishSlurpEventGenerated publishes a SLURP event generation notification
|
|
func (p *PubSub) PublishSlurpEventGenerated(data map[string]interface{}) error {
|
|
return p.PublishHmmmMessage(SlurpEventGenerated, data)
|
|
}
|
|
|
|
// PublishSlurpEventAck publishes a SLURP event acknowledgment
|
|
func (p *PubSub) PublishSlurpEventAck(data map[string]interface{}) error {
|
|
return p.PublishHmmmMessage(SlurpEventAck, data)
|
|
}
|
|
|
|
// PublishSlurpContextUpdate publishes a SLURP context update notification
|
|
func (p *PubSub) PublishSlurpContextUpdate(data map[string]interface{}) error {
|
|
return p.PublishHmmmMessage(SlurpContextUpdate, data)
|
|
}
|
|
|
|
// PublishSlurpIntegrationEvent publishes a generic SLURP integration event
|
|
func (p *PubSub) PublishSlurpIntegrationEvent(eventType string, discussionID string, slurpEvent map[string]interface{}) error {
|
|
data := map[string]interface{}{
|
|
"event_type": eventType,
|
|
"discussion_id": discussionID,
|
|
"slurp_event": slurpEvent,
|
|
"timestamp": time.Now(),
|
|
"source": "hmmm-slurp-integration",
|
|
"peer_id": p.host.ID().String(),
|
|
}
|
|
|
|
return p.PublishSlurpEventGenerated(data)
|
|
}
|
|
|
|
// GetHypercoreLog returns the hypercore logger for external access
|
|
func (p *PubSub) GetHypercoreLog() HypercoreLogger {
|
|
return p.hypercoreLog
|
|
}
|
|
|
|
// MessageOptions holds options for role-based messages
|
|
type MessageOptions struct {
|
|
FromRole string
|
|
ToRoles []string
|
|
RequiredExpertise []string
|
|
ProjectID string
|
|
Priority string
|
|
ThreadID string
|
|
}
|
|
|
|
// handleBzzzMessages processes incoming Bzzz coordination messages
|
|
func (p *PubSub) handleBzzzMessages() {
|
|
for {
|
|
msg, err := p.bzzzSub.Next(p.ctx)
|
|
if err != nil {
|
|
if p.ctx.Err() != nil {
|
|
return // Context cancelled
|
|
}
|
|
fmt.Printf("❌ Error receiving Bzzz message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if msg.ReceivedFrom == p.host.ID() {
|
|
continue
|
|
}
|
|
|
|
var bzzzMsg Message
|
|
if err := json.Unmarshal(msg.Data, &bzzzMsg); err != nil {
|
|
fmt.Printf("❌ Failed to unmarshal Bzzz message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
p.processBzzzMessage(bzzzMsg, msg.ReceivedFrom)
|
|
}
|
|
}
|
|
|
|
// handleHmmmMessages processes incoming HMMM meta-discussion messages
|
|
func (p *PubSub) handleHmmmMessages() {
|
|
for {
|
|
msg, err := p.hmmmSub.Next(p.ctx)
|
|
if err != nil {
|
|
if p.ctx.Err() != nil {
|
|
return // Context cancelled
|
|
}
|
|
fmt.Printf("❌ Error receiving HMMM message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if msg.ReceivedFrom == p.host.ID() {
|
|
continue
|
|
}
|
|
|
|
var hmmmMsg Message
|
|
if err := json.Unmarshal(msg.Data, &hmmmMsg); err != nil {
|
|
fmt.Printf("❌ Failed to unmarshal HMMM message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if p.HmmmMessageHandler != nil {
|
|
p.HmmmMessageHandler(hmmmMsg, msg.ReceivedFrom)
|
|
} else {
|
|
p.processHmmmMessage(hmmmMsg, msg.ReceivedFrom)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleContextFeedbackMessages processes incoming context feedback messages
|
|
func (p *PubSub) handleContextFeedbackMessages() {
|
|
for {
|
|
msg, err := p.contextSub.Next(p.ctx)
|
|
if err != nil {
|
|
if p.ctx.Err() != nil {
|
|
return // Context cancelled
|
|
}
|
|
fmt.Printf("❌ Error receiving Context Feedback message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if msg.ReceivedFrom == p.host.ID() {
|
|
continue
|
|
}
|
|
|
|
var contextMsg Message
|
|
if err := json.Unmarshal(msg.Data, &contextMsg); err != nil {
|
|
fmt.Printf("❌ Failed to unmarshal Context Feedback message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if p.ContextFeedbackHandler != nil {
|
|
p.ContextFeedbackHandler(contextMsg, msg.ReceivedFrom)
|
|
} else {
|
|
p.processContextFeedbackMessage(contextMsg, msg.ReceivedFrom)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleDynamicMessages processes messages from a dynamic topic subscription
|
|
func (p *PubSub) handleDynamicMessages(sub *pubsub.Subscription) {
|
|
for {
|
|
msg, err := sub.Next(p.ctx)
|
|
if err != nil {
|
|
if p.ctx.Err() != nil || err.Error() == "subscription cancelled" {
|
|
return // Subscription was cancelled, exit handler
|
|
}
|
|
fmt.Printf("❌ Error receiving dynamic message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
if msg.ReceivedFrom == p.host.ID() {
|
|
continue
|
|
}
|
|
|
|
var dynamicMsg Message
|
|
if err := json.Unmarshal(msg.Data, &dynamicMsg); err != nil {
|
|
fmt.Printf("❌ Failed to unmarshal dynamic message: %v\n", err)
|
|
continue
|
|
}
|
|
|
|
// Use the main HMMM handler for all dynamic messages
|
|
if p.HmmmMessageHandler != nil {
|
|
p.HmmmMessageHandler(dynamicMsg, msg.ReceivedFrom)
|
|
}
|
|
}
|
|
}
|
|
|
|
// processBzzzMessage handles different types of Bzzz coordination messages
|
|
func (p *PubSub) processBzzzMessage(msg Message, from peer.ID) {
|
|
fmt.Printf("🐝 Bzzz [%s] from %s: %v\n", msg.Type, from.ShortString(), msg.Data)
|
|
|
|
// Log to hypercore if logger is available
|
|
if p.hypercoreLog != nil {
|
|
logData := map[string]interface{}{
|
|
"message_type": string(msg.Type),
|
|
"from_peer": from.String(),
|
|
"from_short": from.ShortString(),
|
|
"timestamp": msg.Timestamp,
|
|
"data": msg.Data,
|
|
"topic": "CHORUS",
|
|
}
|
|
|
|
// Map pubsub message types to hypercore log types
|
|
var logType string
|
|
switch msg.Type {
|
|
case TaskAnnouncement:
|
|
logType = "task_announced"
|
|
case TaskClaim:
|
|
logType = "task_claimed"
|
|
case TaskProgress:
|
|
logType = "task_progress"
|
|
case TaskComplete:
|
|
logType = "task_completed"
|
|
case CapabilityBcast:
|
|
logType = "capability_broadcast"
|
|
case AvailabilityBcast:
|
|
logType = "network_event"
|
|
default:
|
|
logType = "network_event"
|
|
}
|
|
|
|
if err := p.hypercoreLog.AppendString(logType, logData); err != nil {
|
|
fmt.Printf("❌ Failed to log Bzzz message to hypercore: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// processHmmmMessage provides default handling for HMMM messages if no external handler is set
|
|
func (p *PubSub) processHmmmMessage(msg Message, from peer.ID) {
|
|
fmt.Printf("🎯 Default HMMM Handler [%s] from %s: %v\n",
|
|
msg.Type, from.ShortString(), msg.Data)
|
|
|
|
// Log to hypercore if logger is available
|
|
if p.hypercoreLog != nil {
|
|
logData := map[string]interface{}{
|
|
"message_type": string(msg.Type),
|
|
"from_peer": from.String(),
|
|
"from_short": from.ShortString(),
|
|
"timestamp": msg.Timestamp,
|
|
"data": msg.Data,
|
|
"topic": "hmmm",
|
|
"from_role": msg.FromRole,
|
|
"to_roles": msg.ToRoles,
|
|
"required_expertise": msg.RequiredExpertise,
|
|
"project_id": msg.ProjectID,
|
|
"priority": msg.Priority,
|
|
"thread_id": msg.ThreadID,
|
|
}
|
|
|
|
// Map pubsub message types to hypercore log types
|
|
var logType string
|
|
switch msg.Type {
|
|
case MetaDiscussion, TaskHelpRequest, TaskHelpResponse:
|
|
logType = "collaboration"
|
|
case CoordinationRequest, CoordinationComplete:
|
|
logType = "collaboration"
|
|
case DependencyAlert:
|
|
logType = "collaboration"
|
|
case EscalationTrigger:
|
|
logType = "escalation"
|
|
case RoleAnnouncement, ExpertiseRequest, ExpertiseResponse:
|
|
logType = "collaboration"
|
|
case StatusUpdate, WorkAllocation, RoleCollaboration:
|
|
logType = "collaboration"
|
|
case MentorshipRequest, MentorshipResponse:
|
|
logType = "collaboration"
|
|
case ProjectUpdate, DeliverableReady:
|
|
logType = "collaboration"
|
|
default:
|
|
logType = "collaboration"
|
|
}
|
|
|
|
if err := p.hypercoreLog.AppendString(logType, logData); err != nil {
|
|
fmt.Printf("❌ Failed to log HMMM message to hypercore: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// processContextFeedbackMessage provides default handling for context feedback messages if no external handler is set
|
|
func (p *PubSub) processContextFeedbackMessage(msg Message, from peer.ID) {
|
|
fmt.Printf("🧠 Context Feedback [%s] from %s: %v\n",
|
|
msg.Type, from.ShortString(), msg.Data)
|
|
|
|
// Log to hypercore if logger is available
|
|
if p.hypercoreLog != nil {
|
|
logData := map[string]interface{}{
|
|
"message_type": string(msg.Type),
|
|
"from_peer": from.String(),
|
|
"from_short": from.ShortString(),
|
|
"timestamp": msg.Timestamp,
|
|
"data": msg.Data,
|
|
"topic": "context_feedback",
|
|
"from_role": msg.FromRole,
|
|
"to_roles": msg.ToRoles,
|
|
"project_id": msg.ProjectID,
|
|
"priority": msg.Priority,
|
|
"thread_id": msg.ThreadID,
|
|
}
|
|
|
|
// Map context feedback message types to hypercore log types
|
|
var logType string
|
|
switch msg.Type {
|
|
case FeedbackEvent:
|
|
logType = "context_feedback"
|
|
case ContextRequest, ContextResponse:
|
|
logType = "context_request"
|
|
case ContextUsage, ContextRelevance:
|
|
logType = "context_usage"
|
|
default:
|
|
logType = "context_feedback"
|
|
}
|
|
|
|
if err := p.hypercoreLog.AppendString(logType, logData); err != nil {
|
|
fmt.Printf("❌ Failed to log Context Feedback message to hypercore: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close shuts down the PubSub instance
|
|
func (p *PubSub) Close() error {
|
|
p.cancel()
|
|
|
|
if p.bzzzSub != nil {
|
|
p.bzzzSub.Cancel()
|
|
}
|
|
if p.hmmmSub != nil {
|
|
p.hmmmSub.Cancel()
|
|
}
|
|
if p.contextSub != nil {
|
|
p.contextSub.Cancel()
|
|
}
|
|
|
|
if p.bzzzTopic != nil {
|
|
p.bzzzTopic.Close()
|
|
}
|
|
if p.hmmmTopic != nil {
|
|
p.hmmmTopic.Close()
|
|
}
|
|
if p.contextTopic != nil {
|
|
p.contextTopic.Close()
|
|
}
|
|
|
|
p.dynamicTopicsMux.Lock()
|
|
for _, topic := range p.dynamicTopics {
|
|
topic.Close()
|
|
}
|
|
p.dynamicTopicsMux.Unlock()
|
|
|
|
return nil
|
|
}
|