package main import ( "bytes" "context" "encoding/json" "fmt" "log" "net/http" "os" "path/filepath" "reflect" "time" "chorus.services/bzzz/api" "chorus.services/bzzz/coordinator" "chorus.services/bzzz/discovery" "chorus.services/bzzz/logging" "chorus.services/bzzz/p2p" "chorus.services/bzzz/pkg/config" "chorus.services/bzzz/pkg/crypto" "chorus.services/bzzz/pkg/dht" "chorus.services/bzzz/pkg/election" "chorus.services/bzzz/pkg/health" "chorus.services/bzzz/pkg/shutdown" "chorus.services/bzzz/pkg/ucxi" "chorus.services/bzzz/pkg/ucxl" "chorus.services/bzzz/pkg/web" "chorus.services/bzzz/pubsub" "chorus.services/bzzz/reasoning" "chorus.services/hmmm/pkg/hmmm" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" ) // SimpleTaskTracker tracks active tasks for availability reporting type SimpleTaskTracker struct { maxTasks int activeTasks map[string]bool decisionPublisher *ucxl.DecisionPublisher } // GetActiveTasks returns list of active task IDs func (t *SimpleTaskTracker) GetActiveTasks() []string { tasks := make([]string, 0, len(t.activeTasks)) for taskID := range t.activeTasks { tasks = append(tasks, taskID) } return tasks } // GetMaxTasks returns maximum number of concurrent tasks func (t *SimpleTaskTracker) GetMaxTasks() int { return t.maxTasks } // AddTask marks a task as active func (t *SimpleTaskTracker) AddTask(taskID string) { t.activeTasks[taskID] = true } // RemoveTask marks a task as completed and publishes decision if publisher available func (t *SimpleTaskTracker) RemoveTask(taskID string) { delete(t.activeTasks, taskID) // Publish task completion decision if publisher is available if t.decisionPublisher != nil { t.publishTaskCompletion(taskID, true, "Task completed successfully", nil) } } // CompleteTaskWithDecision marks a task as completed and publishes detailed decision func (t *SimpleTaskTracker) CompleteTaskWithDecision(taskID string, success bool, summary string, filesModified []string) { delete(t.activeTasks, taskID) // Publish task completion decision if publisher is available if t.decisionPublisher != nil { t.publishTaskCompletion(taskID, success, summary, filesModified) } } // SetDecisionPublisher sets the decision publisher for task completion tracking func (t *SimpleTaskTracker) SetDecisionPublisher(publisher *ucxl.DecisionPublisher) { t.decisionPublisher = publisher } // publishTaskCompletion publishes a task completion decision to DHT func (t *SimpleTaskTracker) publishTaskCompletion(taskID string, success bool, summary string, filesModified []string) { if t.decisionPublisher == nil { return } if err := t.decisionPublisher.PublishTaskCompletion(taskID, success, summary, filesModified); err != nil { fmt.Printf("⚠️ Failed to publish task completion for %s: %v\n", taskID, err) } else { fmt.Printf("πŸ“€ Published task completion decision for: %s\n", taskID) } } func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() fmt.Println("πŸš€ Starting Bzzz + HMMM P2P Task Coordination System...") // Determine config file path configPath := os.Getenv("BZZZ_CONFIG_PATH") if configPath == "" { configPath = ".bzzz/config.yaml" } // Check if setup is required if config.IsSetupRequired(configPath) { fmt.Println("πŸ”§ Setup required - starting web configuration interface...") startSetupMode(configPath) return } // Load configuration cfg, err := config.LoadConfig(configPath) if err != nil { log.Fatalf("Failed to load configuration: %v", err) } // Validate configuration if !config.IsValidConfiguration(cfg) { fmt.Println("⚠️ Configuration is invalid - starting setup mode...") startSetupMode(configPath) return } // Initialize P2P node node, err := p2p.NewNode(ctx) if err != nil { log.Fatalf("Failed to create P2P node: %v", err) } defer node.Close() // Apply node-specific configuration if agent ID is not set if cfg.Agent.ID == "" { nodeID := node.ID().ShortString() nodeSpecificCfg := config.GetNodeSpecificDefaults(nodeID) // Merge node-specific defaults with loaded config cfg.Agent.ID = nodeSpecificCfg.Agent.ID if len(cfg.Agent.Capabilities) == 0 { cfg.Agent.Capabilities = nodeSpecificCfg.Agent.Capabilities } if len(cfg.Agent.Models) == 0 { cfg.Agent.Models = nodeSpecificCfg.Agent.Models } if cfg.Agent.Specialization == "" { cfg.Agent.Specialization = nodeSpecificCfg.Agent.Specialization } } // Apply role-based configuration if no role is set if cfg.Agent.Role == "" { // Determine default role based on specialization defaultRole := getDefaultRoleForSpecialization(cfg.Agent.Specialization) if defaultRole != "" { fmt.Printf("🎭 Applying default role: %s\n", defaultRole) if err := cfg.ApplyRoleDefinition(defaultRole); err != nil { fmt.Printf("⚠️ Failed to apply role definition: %v\n", err) } else { fmt.Printf("βœ… Role applied: %s\n", cfg.Agent.Role) } } } fmt.Printf("🐝 Bzzz node started successfully\n") fmt.Printf("πŸ“ Node ID: %s\n", node.ID().ShortString()) fmt.Printf("πŸ€– Agent ID: %s\n", cfg.Agent.ID) fmt.Printf("🎯 Specialization: %s\n", cfg.Agent.Specialization) // Display authority level if role is configured if cfg.Agent.Role != "" { authority, err := cfg.GetRoleAuthority(cfg.Agent.Role) if err == nil { fmt.Printf("🎭 Role: %s (Authority: %s)\n", cfg.Agent.Role, authority) if authority == config.AuthorityMaster { fmt.Printf("πŸ‘‘ This node can become admin/SLURP\n") } } } // Hive is deprecated - removed reference fmt.Printf("πŸ”— Listening addresses:\n") for _, addr := range node.Addresses() { fmt.Printf(" %s/p2p/%s\n", addr, node.ID()) } // Initialize Hypercore-style logger hlog := logging.NewHypercoreLog(node.ID()) hlog.Append(logging.PeerJoined, map[string]interface{}{"status": "started"}) fmt.Printf("πŸ“ Hypercore logger initialized\n") // Initialize mDNS discovery mdnsDiscovery, err := discovery.NewMDNSDiscovery(ctx, node.Host(), "bzzz-peer-discovery") if err != nil { log.Fatalf("Failed to create mDNS discovery: %v", err) } defer mdnsDiscovery.Close() // Initialize PubSub with hypercore logging ps, err := pubsub.NewPubSubWithLogger(ctx, node.Host(), "bzzz/coordination/v1", "hmmm/meta-discussion/v1", hlog) if err != nil { log.Fatalf("Failed to create PubSub: %v", err) } defer ps.Close() // Initialize HMMM Router hmmmAdapter := pubsub.NewGossipPublisher(ps) hmmmRouter := hmmm.NewRouter(hmmmAdapter, hmmm.DefaultConfig()) fmt.Printf("🐜 HMMM Router initialized and attached to Bzzz pubsub\n") _ = hmmmRouter // Prevent unused variable error for now // Join role-based topics if role is configured if cfg.Agent.Role != "" { if err := ps.JoinRoleBasedTopics(cfg.Agent.Role, cfg.Agent.Expertise, cfg.Agent.ReportsTo); err != nil { fmt.Printf("⚠️ Failed to join role-based topics: %v\n", err) } else { fmt.Printf("🎯 Joined role-based collaboration topics\n") } } // Optional: HMMM per-issue room smoke test if os.Getenv("BZZZ_HMMM_SMOKE") == "1" { issueID := 42 topic := fmt.Sprintf("bzzz/meta/issue/%d", issueID) if err := ps.JoinDynamicTopic(topic); err != nil { fmt.Printf("⚠️ HMMM smoke: failed to join %s: %v\n", topic, err) } else { seed := map[string]interface{}{ "version": 1, "type": "meta_msg", "issue_id": issueID, "thread_id": fmt.Sprintf("issue-%d", issueID), "msg_id": fmt.Sprintf("seed-%d", time.Now().UnixNano()), "node_id": node.ID().ShortString(), "hop_count": 0, "timestamp": time.Now().UTC(), "message": "Seed: HMMM per-issue room initialized.", } b, _ := json.Marshal(seed) if err := ps.PublishRaw(topic, b); err != nil { fmt.Printf("⚠️ HMMM smoke: publish failed: %v\n", err) } else { fmt.Printf("πŸ§ͺ HMMM smoke: published seed to %s\n", topic) } } } // === Admin Election System === // Initialize election manager electionManager := election.NewElectionManager(ctx, cfg, node.Host(), ps, node.ID().ShortString()) // Set election callbacks electionManager.SetCallbacks( func(oldAdmin, newAdmin string) { fmt.Printf("πŸ‘‘ Admin changed: %s -> %s\n", oldAdmin, newAdmin) // If this node becomes admin, enable SLURP functionality if newAdmin == node.ID().ShortString() { fmt.Printf("🎯 This node is now admin - enabling SLURP functionality\n") cfg.Slurp.Enabled = true // Apply admin role configuration if err := cfg.ApplyRoleDefinition("admin"); err != nil { fmt.Printf("⚠️ Failed to apply admin role: %v\n", err) } } }, func(winner string) { fmt.Printf("πŸ† Election completed, winner: %s\n", winner) }, ) // Start election manager (now handles heartbeat lifecycle internally) if err := electionManager.Start(); err != nil { fmt.Printf("❌ Failed to start election manager: %v\n", err) } else { fmt.Printf("βœ… Election manager started with automated heartbeat management\n") } defer electionManager.Stop() // ============================ // === DHT Storage and Decision Publishing === // Initialize DHT for distributed storage var dhtNode *dht.LibP2PDHT var encryptedStorage *dht.EncryptedDHTStorage var decisionPublisher *ucxl.DecisionPublisher if cfg.V2.DHT.Enabled { // Create DHT dhtNode, err = dht.NewLibP2PDHT(ctx, node.Host()) if err != nil { fmt.Printf("⚠️ Failed to create DHT: %v\n", err) } else { fmt.Printf("πŸ•ΈοΈ DHT initialized\n") // Bootstrap DHT if err := dhtNode.Bootstrap(); err != nil { fmt.Printf("⚠️ DHT bootstrap failed: %v\n", err) } // Connect to bootstrap peers if configured for _, addrStr := range cfg.V2.DHT.BootstrapPeers { addr, err := multiaddr.NewMultiaddr(addrStr) if err != nil { fmt.Printf("⚠️ Invalid bootstrap address %s: %v\n", addrStr, err) continue } // Extract peer info from multiaddr info, err := peer.AddrInfoFromP2pAddr(addr) if err != nil { fmt.Printf("⚠️ Failed to parse peer info from %s: %v\n", addrStr, err) continue } if err := node.Host().Connect(ctx, *info); err != nil { fmt.Printf("⚠️ Failed to connect to bootstrap peer %s: %v\n", addrStr, err) } else { fmt.Printf("πŸ”— Connected to DHT bootstrap peer: %s\n", addrStr) } } // Initialize encrypted storage encryptedStorage = dht.NewEncryptedDHTStorage( ctx, node.Host(), dhtNode, cfg, node.ID().ShortString(), ) // Start cache cleanup encryptedStorage.StartCacheCleanup(5 * time.Minute) fmt.Printf("πŸ” Encrypted DHT storage initialized\n") // Initialize decision publisher decisionPublisher = ucxl.NewDecisionPublisher( ctx, cfg, encryptedStorage, node.ID().ShortString(), cfg.Agent.ID, ) fmt.Printf("πŸ“€ Decision publisher initialized\n") // Test the encryption system on startup go func() { time.Sleep(2 * time.Second) // Wait for initialization if err := crypto.TestAgeEncryption(); err != nil { fmt.Printf("❌ Age encryption test failed: %v\n", err) } else { fmt.Printf("βœ… Age encryption test passed\n") } if err := crypto.TestShamirSecretSharing(); err != nil { fmt.Printf("❌ Shamir secret sharing test failed: %v\n", err) } else { fmt.Printf("βœ… Shamir secret sharing test passed\n") } // Test end-to-end encrypted decision flow time.Sleep(3 * time.Second) // Wait a bit more testEndToEndDecisionFlow(decisionPublisher, encryptedStorage) }() } } else { fmt.Printf("βšͺ DHT disabled in configuration\n") } defer func() { if dhtNode != nil { dhtNode.Close() } }() // =========================================== // === Task Coordination Integration === // Initialize Task Coordinator taskCoordinator := coordinator.NewTaskCoordinator( ctx, ps, hlog, cfg, node.ID().ShortString(), hmmmRouter, ) // Start task coordination taskCoordinator.Start() fmt.Printf("βœ… Task coordination system active\n") // ========================== // Start HTTP API server httpServer := api.NewHTTPServer(8080, hlog, ps) go func() { if err := httpServer.Start(); err != nil && err != http.ErrServerClosed { fmt.Printf("❌ HTTP server error: %v\n", err) } }() defer httpServer.Stop() fmt.Printf("🌐 HTTP API server started on :8080\n") // === UCXI Server Integration === // Initialize UCXI server if UCXL protocol is enabled var ucxiServer *ucxi.Server if cfg.UCXL.Enabled && cfg.UCXL.Server.Enabled { // Create storage directory storageDir := cfg.UCXL.Storage.Directory if storageDir == "" { storageDir = filepath.Join(os.TempDir(), "bzzz-ucxi-storage") } storage, err := ucxi.NewBasicContentStorage(storageDir) if err != nil { fmt.Printf("⚠️ Failed to create UCXI storage: %v\n", err) } else { // Create resolver resolver := ucxi.NewBasicAddressResolver(node.ID().ShortString()) resolver.SetDefaultTTL(cfg.UCXL.Resolution.CacheTTL) // TODO: Add P2P integration hooks here // resolver.SetAnnounceHook(...) // resolver.SetDiscoverHook(...) // Create UCXI server ucxiConfig := ucxi.ServerConfig{ Port: cfg.UCXL.Server.Port, BasePath: cfg.UCXL.Server.BasePath, Resolver: resolver, Storage: storage, Logger: ucxi.SimpleLogger{}, } ucxiServer = ucxi.NewServer(ucxiConfig) go func() { if err := ucxiServer.Start(); err != nil && err != http.ErrServerClosed { fmt.Printf("❌ UCXI server error: %v\n", err) } }() defer func() { if ucxiServer != nil { ucxiServer.Stop() } }() fmt.Printf("πŸ”— UCXI server started on :%d%s/ucxi/v1\n", cfg.UCXL.Server.Port, cfg.UCXL.Server.BasePath) } } else { fmt.Printf("βšͺ UCXI server disabled (UCXL protocol not enabled)\n") } // ============================ // Create simple task tracker taskTracker := &SimpleTaskTracker{ maxTasks: cfg.Agent.MaxTasks, activeTasks: make(map[string]bool), } // Connect decision publisher to task tracker if available if decisionPublisher != nil { taskTracker.SetDecisionPublisher(decisionPublisher) fmt.Printf("πŸ“€ Task completion decisions will be published to DHT\n") } // Announce capabilities and role go announceAvailability(ps, node.ID().ShortString(), taskTracker) go announceCapabilitiesOnChange(ps, node.ID().ShortString(), cfg) go announceRoleOnStartup(ps, node.ID().ShortString(), cfg) // Start status reporting go statusReporter(node) fmt.Printf("πŸ” Listening for peers on local network...\n") fmt.Printf("πŸ“‘ Ready for task coordination and meta-discussion\n") fmt.Printf("🎯 HMMM collaborative reasoning enabled\n") // === Comprehensive Health Monitoring & Graceful Shutdown === // Initialize shutdown manager shutdownManager := shutdown.NewManager(30*time.Second, &simpleLogger{}) // Initialize health manager healthManager := health.NewManager(node.ID().ShortString(), "v0.2.0", &simpleLogger{}) healthManager.SetShutdownManager(shutdownManager) // Register health checks setupHealthChecks(healthManager, ps, node, dhtNode) // Register components for graceful shutdown setupGracefulShutdown(shutdownManager, healthManager, node, ps, mdnsDiscovery, electionManager, httpServer, ucxiServer, taskCoordinator, dhtNode) // Start health monitoring if err := healthManager.Start(); err != nil { log.Printf("❌ Failed to start health manager: %v", err) } else { fmt.Printf("❀️ Health monitoring started\n") } // Start health HTTP server on port 8081 if err := healthManager.StartHTTPServer(8081); err != nil { log.Printf("❌ Failed to start health HTTP server: %v", err) } else { fmt.Printf("πŸ₯ Health endpoints available at http://localhost:8081/health\n") } // Start shutdown manager (begins listening for signals) shutdownManager.Start() fmt.Printf("πŸ›‘οΈ Graceful shutdown manager started\n") fmt.Printf("βœ… Bzzz system fully operational with health monitoring\n") // Wait for graceful shutdown shutdownManager.Wait() fmt.Println("βœ… Bzzz system shutdown completed") } // setupHealthChecks configures comprehensive health monitoring func setupHealthChecks(healthManager *health.Manager, ps *pubsub.PubSub, node *p2p.Node, dhtNode *dht.LibP2PDHT) { // P2P connectivity check (critical) p2pCheck := &health.HealthCheck{ Name: "p2p-connectivity", Description: "P2P network connectivity and peer count", Enabled: true, Critical: true, Interval: 15 * time.Second, Timeout: 10 * time.Second, Checker: func(ctx context.Context) health.CheckResult { connectedPeers := node.ConnectedPeers() minPeers := 1 if connectedPeers < minPeers { return health.CheckResult{ Healthy: false, Message: fmt.Sprintf("Insufficient P2P peers: %d < %d", connectedPeers, minPeers), Details: map[string]interface{}{ "connected_peers": connectedPeers, "min_peers": minPeers, "node_id": node.ID().ShortString(), }, Timestamp: time.Now(), } } return health.CheckResult{ Healthy: true, Message: fmt.Sprintf("P2P connectivity OK: %d peers connected", connectedPeers), Details: map[string]interface{}{ "connected_peers": connectedPeers, "min_peers": minPeers, "node_id": node.ID().ShortString(), }, Timestamp: time.Now(), } }, } healthManager.RegisterCheck(p2pCheck) // Active PubSub health probe pubsubAdapter := health.NewPubSubAdapter(ps) activePubSubCheck := health.CreateActivePubSubCheck(pubsubAdapter) healthManager.RegisterCheck(activePubSubCheck) fmt.Printf("βœ… Active PubSub health probe registered\n") // Active DHT health probe (if DHT is enabled) if dhtNode != nil { dhtAdapter := health.NewDHTAdapter(dhtNode) activeDHTCheck := health.CreateActiveDHTCheck(dhtAdapter) healthManager.RegisterCheck(activeDHTCheck) fmt.Printf("βœ… Active DHT health probe registered\n") } // Legacy static health checks for backward compatibility // PubSub system check (static) pubsubCheck := &health.HealthCheck{ Name: "pubsub-system-static", Description: "Static PubSub messaging system health", Enabled: true, Critical: false, Interval: 30 * time.Second, Timeout: 5 * time.Second, Checker: func(ctx context.Context) health.CheckResult { // Simple health check - basic connectivity return health.CheckResult{ Healthy: true, Message: "PubSub system operational (static check)", Timestamp: time.Now(), } }, } healthManager.RegisterCheck(pubsubCheck) // DHT system check (static, if DHT is enabled) if dhtNode != nil { dhtCheck := &health.HealthCheck{ Name: "dht-system-static", Description: "Static Distributed Hash Table system health", Enabled: true, Critical: false, Interval: 60 * time.Second, Timeout: 15 * time.Second, Checker: func(ctx context.Context) health.CheckResult { // Basic connectivity check return health.CheckResult{ Healthy: true, Message: "DHT system operational (static check)", Details: map[string]interface{}{ "dht_enabled": true, }, Timestamp: time.Now(), } }, } healthManager.RegisterCheck(dhtCheck) } // Memory usage check memoryCheck := health.CreateMemoryCheck(0.85) // Alert if > 85% healthManager.RegisterCheck(memoryCheck) // Disk space check diskCheck := health.CreateDiskSpaceCheck("/tmp", 0.90) // Alert if > 90% healthManager.RegisterCheck(diskCheck) } // setupGracefulShutdown registers all components for proper shutdown func setupGracefulShutdown(shutdownManager *shutdown.Manager, healthManager *health.Manager, node *p2p.Node, ps *pubsub.PubSub, mdnsDiscovery interface{}, electionManager interface{}, httpServer *api.HTTPServer, ucxiServer *ucxi.Server, taskCoordinator interface{}, dhtNode *dht.LibP2PDHT) { // Health manager (stop health checks early) healthComponent := shutdown.NewGenericComponent("health-manager", 10, true). SetShutdownFunc(func(ctx context.Context) error { return healthManager.Stop() }) shutdownManager.Register(healthComponent) // HTTP servers if httpServer != nil { httpComponent := shutdown.NewGenericComponent("main-http-server", 20, true). SetShutdownFunc(func(ctx context.Context) error { return httpServer.Stop() }) shutdownManager.Register(httpComponent) } if ucxiServer != nil { ucxiComponent := shutdown.NewGenericComponent("ucxi-server", 21, true). SetShutdownFunc(func(ctx context.Context) error { ucxiServer.Stop() return nil }) shutdownManager.Register(ucxiComponent) } // Task coordination system if taskCoordinator != nil { taskComponent := shutdown.NewGenericComponent("task-coordinator", 30, true). SetCloser(func() error { // In real implementation, gracefully stop task coordinator return nil }) shutdownManager.Register(taskComponent) } // DHT system if dhtNode != nil { dhtComponent := shutdown.NewGenericComponent("dht-node", 35, true). SetCloser(func() error { return dhtNode.Close() }) shutdownManager.Register(dhtComponent) } // PubSub system if ps != nil { pubsubComponent := shutdown.NewGenericComponent("pubsub-system", 40, true). SetCloser(func() error { return ps.Close() }) shutdownManager.Register(pubsubComponent) } // mDNS discovery if mdnsDiscovery != nil { mdnsComponent := shutdown.NewGenericComponent("mdns-discovery", 50, true). SetCloser(func() error { // In real implementation, close mDNS discovery properly return nil }) shutdownManager.Register(mdnsComponent) } // P2P node (close last as other components depend on it) p2pComponent := shutdown.NewP2PNodeComponent("p2p-node", func() error { return node.Close() }, 60) shutdownManager.Register(p2pComponent) // Add shutdown hooks setupShutdownHooks(shutdownManager) } // setupShutdownHooks adds hooks for different shutdown phases func setupShutdownHooks(shutdownManager *shutdown.Manager) { // Pre-shutdown: Save state and notify peers shutdownManager.AddHook(shutdown.PhasePreShutdown, func(ctx context.Context) error { fmt.Println("πŸ”„ Pre-shutdown: Notifying peers and saving state...") // In real implementation: notify peers, save critical state return nil }) // Post-shutdown: Final cleanup shutdownManager.AddHook(shutdown.PhasePostShutdown, func(ctx context.Context) error { fmt.Println("πŸ”„ Post-shutdown: Performing final cleanup...") // In real implementation: flush logs, clean temporary files return nil }) // Cleanup: Final state persistence shutdownManager.AddHook(shutdown.PhaseCleanup, func(ctx context.Context) error { fmt.Println("πŸ”„ Cleanup: Finalizing shutdown...") // In real implementation: persist final state, cleanup resources return nil }) } // simpleLogger implements basic logging for shutdown and health systems type simpleLogger struct{} func (l *simpleLogger) Info(msg string, args ...interface{}) { fmt.Printf("[INFO] "+msg+"\n", args...) } func (l *simpleLogger) Warn(msg string, args ...interface{}) { fmt.Printf("[WARN] "+msg+"\n", args...) } func (l *simpleLogger) Error(msg string, args ...interface{}) { fmt.Printf("[ERROR] "+msg+"\n", args...) } // announceAvailability broadcasts current working status for task assignment func announceAvailability(ps *pubsub.PubSub, nodeID string, taskTracker *SimpleTaskTracker) { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for ; ; <-ticker.C { currentTasks := taskTracker.GetActiveTasks() maxTasks := taskTracker.GetMaxTasks() isAvailable := len(currentTasks) < maxTasks status := "ready" if len(currentTasks) >= maxTasks { status = "busy" } else if len(currentTasks) > 0 { status = "working" } availability := map[string]interface{}{ "node_id": nodeID, "available_for_work": isAvailable, "current_tasks": len(currentTasks), "max_tasks": maxTasks, "last_activity": time.Now().Unix(), "status": status, "timestamp": time.Now().Unix(), } if err := ps.PublishBzzzMessage(pubsub.AvailabilityBcast, availability); err != nil { fmt.Printf("❌ Failed to announce availability: %v\n", err) } } } // detectAvailableOllamaModels queries Ollama API for available models func detectAvailableOllamaModels() ([]string, error) { resp, err := http.Get("http://localhost:11434/api/tags") if err != nil { return nil, fmt.Errorf("failed to connect to Ollama API: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Ollama API returned status %d", resp.StatusCode) } var tagsResponse struct { Models []struct { Name string `json:"name"` } `json:"models"` } if err := json.NewDecoder(resp.Body).Decode(&tagsResponse); err != nil { return nil, fmt.Errorf("failed to decode Ollama response: %w", err) } models := make([]string, 0, len(tagsResponse.Models)) for _, model := range tagsResponse.Models { models = append(models, model.Name) } return models, nil } // selectBestModel calls the model selection webhook to choose the best model for a prompt func selectBestModel(webhookURL string, availableModels []string, prompt string) (string, error) { if webhookURL == "" || len(availableModels) == 0 { // Fallback to first available model if len(availableModels) > 0 { return availableModels[0], nil } return "", fmt.Errorf("no models available") } requestPayload := map[string]interface{}{ "models": availableModels, "prompt": prompt, } payloadBytes, err := json.Marshal(requestPayload) if err != nil { // Fallback on error return availableModels[0], nil } resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payloadBytes)) if err != nil { // Fallback on error return availableModels[0], nil } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { // Fallback on error return availableModels[0], nil } var response struct { Model string `json:"model"` } if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { // Fallback on error return availableModels[0], nil } // Validate that the returned model is in our available list for _, model := range availableModels { if model == response.Model { return response.Model, nil } } // Fallback if webhook returned invalid model return availableModels[0], nil } // announceCapabilitiesOnChange broadcasts capabilities only when they change func announceCapabilitiesOnChange(ps *pubsub.PubSub, nodeID string, cfg *config.Config) { // Detect available Ollama models and update config availableModels, err := detectAvailableOllamaModels() if err != nil { fmt.Printf("⚠️ Failed to detect Ollama models: %v\n", err) fmt.Printf("πŸ”„ Using configured models: %v\n", cfg.Agent.Models) } else { // Filter configured models to only include available ones validModels := make([]string, 0) for _, configModel := range cfg.Agent.Models { for _, availableModel := range availableModels { if configModel == availableModel { validModels = append(validModels, configModel) break } } } if len(validModels) == 0 { fmt.Printf("⚠️ No configured models available in Ollama, using first available: %v\n", availableModels) if len(availableModels) > 0 { validModels = []string{availableModels[0]} } } else { fmt.Printf("βœ… Available models: %v\n", validModels) } // Update config with available models cfg.Agent.Models = validModels // Configure reasoning module with available models and webhook reasoning.SetModelConfig(validModels, cfg.Agent.ModelSelectionWebhook, cfg.Agent.DefaultReasoningModel) } // Get current capabilities currentCaps := map[string]interface{}{ "node_id": nodeID, "capabilities": cfg.Agent.Capabilities, "models": cfg.Agent.Models, "version": "0.2.0", "specialization": cfg.Agent.Specialization, } // Load stored capabilities from file storedCaps, err := loadStoredCapabilities(nodeID) if err != nil { fmt.Printf("πŸ“„ No stored capabilities found, treating as first run\n") storedCaps = nil } // Check if capabilities have changed if capabilitiesChanged(currentCaps, storedCaps) { fmt.Printf("πŸ”„ Capabilities changed, broadcasting update\n") currentCaps["timestamp"] = time.Now().Unix() currentCaps["reason"] = getChangeReason(currentCaps, storedCaps) // Broadcast the change if err := ps.PublishBzzzMessage(pubsub.CapabilityBcast, currentCaps); err != nil { fmt.Printf("❌ Failed to announce capabilities: %v\n", err) } else { // Store new capabilities if err := storeCapabilities(nodeID, currentCaps); err != nil { fmt.Printf("❌ Failed to store capabilities: %v\n", err) } } } else { fmt.Printf("βœ… Capabilities unchanged since last run\n") } } // statusReporter provides periodic status updates func statusReporter(node *p2p.Node) { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for ; ; <-ticker.C { peers := node.ConnectedPeers() fmt.Printf("πŸ“Š Status: %d connected peers\n", peers) } } // getCapabilitiesFile returns the path to store capabilities for a node func getCapabilitiesFile(nodeID string) string { homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, ".config", "bzzz", fmt.Sprintf("capabilities-%s.json", nodeID)) } // loadStoredCapabilities loads previously stored capabilities from disk func loadStoredCapabilities(nodeID string) (map[string]interface{}, error) { capFile := getCapabilitiesFile(nodeID) data, err := os.ReadFile(capFile) if err != nil { return nil, err } var capabilities map[string]interface{} if err := json.Unmarshal(data, &capabilities); err != nil { return nil, err } return capabilities, nil } // storeCapabilities saves current capabilities to disk func storeCapabilities(nodeID string, capabilities map[string]interface{}) error { capFile := getCapabilitiesFile(nodeID) // Ensure directory exists if err := os.MkdirAll(filepath.Dir(capFile), 0755); err != nil { return err } data, err := json.MarshalIndent(capabilities, "", " ") if err != nil { return err } return os.WriteFile(capFile, data, 0644) } // capabilitiesChanged compares current and stored capabilities func capabilitiesChanged(current, stored map[string]interface{}) bool { if stored == nil { return true // First run, always announce } // Compare important fields that indicate capability changes compareFields := []string{"capabilities", "models", "specialization"} for _, field := range compareFields { if !reflect.DeepEqual(current[field], stored[field]) { return true } } return false } // getChangeReason determines why capabilities changed func getChangeReason(current, stored map[string]interface{}) string { if stored == nil { return "startup" } if !reflect.DeepEqual(current["models"], stored["models"]) { return "model_change" } if !reflect.DeepEqual(current["capabilities"], stored["capabilities"]) { return "capability_change" } if !reflect.DeepEqual(current["specialization"], stored["specialization"]) { return "specialization_change" } return "unknown_change" } // getDefaultRoleForSpecialization maps specializations to default roles func getDefaultRoleForSpecialization(specialization string) string { roleMap := map[string]string{ "code_generation": "backend_developer", "advanced_reasoning": "senior_software_architect", "code_analysis": "security_expert", "general_developer": "full_stack_engineer", "debugging": "qa_engineer", "frontend": "frontend_developer", "backend": "backend_developer", "devops": "devops_engineer", "security": "security_expert", "design": "ui_ux_designer", "architecture": "senior_software_architect", } if role, exists := roleMap[specialization]; exists { return role } // Default fallback return "full_stack_engineer" } // announceRoleOnStartup announces the agent's role when starting up func announceRoleOnStartup(ps *pubsub.PubSub, nodeID string, cfg *config.Config) { if cfg.Agent.Role == "" { return // No role to announce } roleData := map[string]interface{}{ "node_id": nodeID, "role": cfg.Agent.Role, "expertise": cfg.Agent.Expertise, "reports_to": cfg.Agent.ReportsTo, "deliverables": cfg.Agent.Deliverables, "capabilities": cfg.Agent.Capabilities, "specialization": cfg.Agent.Specialization, "timestamp": time.Now().Unix(), "status": "online", } opts := pubsub.MessageOptions{ FromRole: cfg.Agent.Role, RequiredExpertise: cfg.Agent.Expertise, Priority: "medium", } if err := ps.PublishRoleBasedMessage(pubsub.RoleAnnouncement, roleData, opts); err != nil { fmt.Printf("❌ Failed to announce role: %v\n", err) } else { fmt.Printf("πŸ“’ Role announced: %s\n", cfg.Agent.Role) } } // testEndToEndDecisionFlow tests the complete encrypted decision publishing and retrieval flow func testEndToEndDecisionFlow(publisher *ucxl.DecisionPublisher, storage *dht.EncryptedDHTStorage) { if publisher == nil || storage == nil { fmt.Printf("βšͺ Skipping end-to-end test (components not initialized)\n") return } fmt.Printf("πŸ§ͺ Testing end-to-end encrypted decision flow...\n") // Test 1: Publish an architectural decision err := publisher.PublishArchitecturalDecision( "implement_unified_bzzz_slurp", "Integrate SLURP as specialized BZZZ agent with admin role for unified P2P architecture", "Eliminates separate system complexity and leverages existing P2P infrastructure", []string{"Keep separate systems", "Use different consensus algorithm"}, []string{"Single point of coordination", "Improved failover", "Simplified deployment"}, []string{"Test consensus elections", "Implement key reconstruction", "Deploy to cluster"}, ) if err != nil { fmt.Printf("❌ Failed to publish architectural decision: %v\n", err) return } fmt.Printf("βœ… Published architectural decision\n") // Test 2: Publish a code decision testResults := &ucxl.TestResults{ Passed: 15, Failed: 2, Skipped: 1, Coverage: 78.5, FailedTests: []string{"TestElection_SplitBrain", "TestCrypto_KeyReconstruction"}, } err = publisher.PublishCodeDecision( "implement_age_encryption", "Implemented Age encryption for role-based UCXL content security", []string{"pkg/crypto/age_crypto.go", "pkg/dht/encrypted_storage.go"}, 578, testResults, []string{"filippo.io/age", "github.com/libp2p/go-libp2p-kad-dht"}, ) if err != nil { fmt.Printf("❌ Failed to publish code decision: %v\n", err) return } fmt.Printf("βœ… Published code decision\n") // Test 3: Query recent decisions time.Sleep(1 * time.Second) // Allow decisions to propagate decisions, err := publisher.QueryRecentDecisions("", "", "", 10, time.Now().Add(-1*time.Hour)) if err != nil { fmt.Printf("❌ Failed to query recent decisions: %v\n", err) return } fmt.Printf("πŸ” Found %d recent decisions:\n", len(decisions)) for i, metadata := range decisions { fmt.Printf(" %d. %s (creator: %s, type: %s)\n", i+1, metadata.Address, metadata.CreatorRole, metadata.ContentType) } // Test 4: Retrieve and decrypt a specific decision if len(decisions) > 0 { decision, err := publisher.GetDecisionContent(decisions[0].Address) if err != nil { fmt.Printf("❌ Failed to retrieve decision content: %v\n", err) } else { fmt.Printf("βœ… Retrieved decision: %s (%s)\n", decision.Task, decision.Decision) fmt.Printf(" Files modified: %d, Success: %t\n", len(decision.FilesModified), decision.Success) } } // Test 5: Publish system status metrics := map[string]interface{}{ "uptime_seconds": 300, "active_peers": 3, "dht_entries": len(decisions), "encryption_ops": 25, "decryption_ops": 8, "memory_usage_mb": 145.7, } healthChecks := map[string]bool{ "dht_connected": true, "elections_ready": true, "crypto_functional": true, "peers_discovered": true, } err = publisher.PublishSystemStatus("All systems operational - Phase 2B implementation complete", metrics, healthChecks) if err != nil { fmt.Printf("❌ Failed to publish system status: %v\n", err) } else { fmt.Printf("βœ… Published system status\n") } fmt.Printf("πŸŽ‰ End-to-end encrypted decision flow test completed successfully!\n") fmt.Printf("πŸ” All decisions encrypted with role-based Age encryption\n") fmt.Printf("πŸ•ΈοΈ Content stored in distributed DHT with local caching\n") fmt.Printf("πŸ” Content discoverable and retrievable by authorized roles\n") } // startSetupMode starts the web-based configuration interface func startSetupMode(configPath string) { fmt.Println("🌐 Starting web-based setup interface...") fmt.Printf("πŸ“‚ Configuration will be saved to: %s\n", configPath) // Create setup-only HTTP server setupServer := &http.Server{ Addr: ":8090", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Setup routes http.HandleFunc("/", handleSetupUI) http.HandleFunc("/setup/", handleSetupUI) // API routes for setup setupManager := api.NewSetupManager(configPath) http.HandleFunc("/api/setup/required", corsHandler(handleSetupRequired(setupManager))) http.HandleFunc("/api/setup/system", corsHandler(handleSystemDetection(setupManager))) http.HandleFunc("/api/setup/repository/validate", corsHandler(handleRepositoryValidation(setupManager))) http.HandleFunc("/api/setup/repository/providers", corsHandler(handleRepositoryProviders(setupManager))) http.HandleFunc("/api/setup/license/validate", corsHandler(handleLicenseValidation(setupManager))) http.HandleFunc("/api/setup/validate", corsHandler(handleConfigValidation(setupManager))) http.HandleFunc("/api/setup/save", corsHandler(handleConfigSave(setupManager))) http.HandleFunc("/api/setup/discover-machines", corsHandler(handleDiscoverMachines(setupManager))) http.HandleFunc("/api/setup/test-ssh", corsHandler(handleTestSSH(setupManager))) http.HandleFunc("/api/setup/deploy-service", corsHandler(handleDeployService(setupManager))) http.HandleFunc("/api/health", corsHandler(handleSetupHealth)) fmt.Printf("🎯 Setup interface available at: http://localhost:8090\n") fmt.Printf("πŸ”§ Complete the setup to start BZZZ in full mode\n") fmt.Printf("πŸ“„ Press Ctrl+C to exit setup\n") if err := setupServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Setup server failed: %v", err) } } // handleSetupUI serves the embedded web UI or fallback page func handleSetupUI(w http.ResponseWriter, r *http.Request) { // Enable CORS w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } // Determine file path path := r.URL.Path if path == "/" || path == "/setup" || path == "/setup/" { path = "/index.html" } // Remove /setup prefix if present if len(path) > 6 && path[:6] == "/setup" { path = path[6:] } // Try to serve from embedded file system if web.IsEmbeddedFileAvailable(path) { web.ServeEmbeddedFile(w, r, path) } else if web.IsEmbeddedFileAvailable("index.html") { // Fallback to index.html for SPA routing web.ServeEmbeddedFile(w, r, "index.html") } else { // Serve placeholder page if web UI not built web.ServeEmbeddedFile(w, r, "index.html") } } // corsHandler wraps handlers with CORS support func corsHandler(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } handler(w, r) } } // Setup API handlers (simplified versions) func handleSetupRequired(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") response := map[string]interface{}{ "setup_required": sm.IsSetupRequired(), "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) } } func handleSystemDetection(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") systemInfo, err := sm.DetectSystemInfo() if err != nil { http.Error(w, fmt.Sprintf("System detection failed: %v", err), http.StatusInternalServerError) return } response := map[string]interface{}{ "system_info": systemInfo, "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) } } func handleRepositoryValidation(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var repoConfig api.RepositoryConfig if err := json.NewDecoder(r.Body).Decode(&repoConfig); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } if err := sm.ValidateRepositoryConfig(&repoConfig); err != nil { response := map[string]interface{}{ "valid": false, "error": err.Error(), "timestamp": time.Now().Unix(), } w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response) return } response := map[string]interface{}{ "valid": true, "message": "Repository configuration is valid", "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) } } func handleRepositoryProviders(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") providers := sm.GetSupportedProviders() response := map[string]interface{}{ "providers": providers, "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) } } func handleConfigValidation(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var setupConfig api.SetupConfig if err := json.NewDecoder(r.Body).Decode(&setupConfig); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } errors := []string{} if setupConfig.AgentID == "" { errors = append(errors, "agent_id is required") } if len(setupConfig.Capabilities) == 0 { errors = append(errors, "at least one capability is required") } if len(setupConfig.Models) == 0 { errors = append(errors, "at least one model is required") } if setupConfig.Repository != nil { if err := sm.ValidateRepositoryConfig(setupConfig.Repository); err != nil { errors = append(errors, fmt.Sprintf("repository configuration: %v", err)) } } if len(errors) > 0 { response := map[string]interface{}{ "valid": false, "errors": errors, "timestamp": time.Now().Unix(), } w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response) return } response := map[string]interface{}{ "valid": true, "message": "Configuration is valid", "timestamp": time.Now().Unix(), } json.NewEncoder(w).Encode(response) } } func handleConfigSave(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var setupConfig api.SetupConfig if err := json.NewDecoder(r.Body).Decode(&setupConfig); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } if setupConfig.AgentID == "" { http.Error(w, "agent_id is required", http.StatusBadRequest) return } if setupConfig.Repository != nil { if err := sm.ValidateRepositoryConfig(setupConfig.Repository); err != nil { http.Error(w, fmt.Sprintf("Invalid repository configuration: %v", err), http.StatusBadRequest) return } } if err := sm.SaveConfiguration(&setupConfig); err != nil { http.Error(w, fmt.Sprintf("Failed to save configuration: %v", err), http.StatusInternalServerError) return } response := map[string]interface{}{ "success": true, "message": "Configuration saved successfully", "timestamp": time.Now().Unix(), "restart_required": true, } json.NewEncoder(w).Encode(response) } } func handleLicenseValidation(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var licenseRequest struct { Email string `json:"email"` LicenseKey string `json:"licenseKey"` OrganizationName string `json:"organizationName,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&licenseRequest); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } // Validate input if licenseRequest.Email == "" { response := map[string]interface{}{ "valid": false, "message": "Email is required", "timestamp": time.Now().Unix(), } w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response) return } if licenseRequest.LicenseKey == "" { response := map[string]interface{}{ "valid": false, "message": "License key is required", "timestamp": time.Now().Unix(), } w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(response) return } // Mock license validation logic // In production, this would call a license server API validLicenseKey := "BZZZ-2025-DEMO-EVAL-001" if licenseRequest.LicenseKey != validLicenseKey { response := map[string]interface{}{ "valid": false, "message": "Invalid license key. Please check your license key and try again.", "timestamp": time.Now().Unix(), } w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(response) return } // License is valid - return success with details response := map[string]interface{}{ "valid": true, "message": "License validated successfully", "timestamp": time.Now().Unix(), "details": map[string]interface{}{ "licenseType": "Evaluation", "maxNodes": "5", "expiresAt": "2025-12-31", "features": []string{ "Distributed Task Coordination", "AI Model Integration", "Repository Management", "Cluster Formation", "Security Features", }, }, } json.NewEncoder(w).Encode(response) } } func handleSetupHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") health := map[string]interface{}{ "status": "setup_mode", "timestamp": time.Now().Unix(), "web_ui": web.IsEmbeddedFileAvailable("index.html"), } json.NewEncoder(w).Encode(health) } // handleDiscoverMachines discovers machines on the network subnet func handleDiscoverMachines(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var request struct { Subnet string `json:"subnet"` SSHKey string `json:"sshKey"` } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } machines, err := sm.DiscoverNetworkMachines(request.Subnet, request.SSHKey) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "error": err.Error(), "machines": []interface{}{}, }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "machines": machines, }) } } // handleTestSSH tests SSH connection to a machine func handleTestSSH(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var request struct { IP string `json:"ip"` SSHKey string `json:"sshKey"` SSHUsername string `json:"sshUsername"` SSHPassword string `json:"sshPassword"` SSHPort int `json:"sshPort"` } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } result, err := sm.TestSSHConnection(request.IP, request.SSHKey, request.SSHUsername, request.SSHPassword, request.SSHPort) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "error": err.Error(), }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } } // handleDeployService deploys BZZZ service to a machine func handleDeployService(sm *api.SetupManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var request struct { IP string `json:"ip"` SSHKey string `json:"sshKey"` SSHUsername string `json:"sshUsername"` SSHPassword string `json:"sshPassword"` SSHPort int `json:"sshPort"` Config struct { Ports struct { API int `json:"api"` MCP int `json:"mcp"` WebUI int `json:"webui"` P2P int `json:"p2p"` } `json:"ports"` Security interface{} `json:"security"` AutoStart bool `json:"autoStart"` } `json:"config"` } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } // Convert the struct config to a map[string]interface{} format that the backend expects configMap := map[string]interface{}{ "ports": map[string]interface{}{ "api": request.Config.Ports.API, "mcp": request.Config.Ports.MCP, "webui": request.Config.Ports.WebUI, "p2p": request.Config.Ports.P2P, }, "security": request.Config.Security, "autoStart": request.Config.AutoStart, } result, err := sm.DeployServiceToMachine(request.IP, request.SSHKey, request.SSHUsername, request.SSHPassword, request.SSHPort, configMap) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": false, "error": err.Error(), }) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } }