feat: Replace capability broadcasting with availability broadcasting
- Add availability broadcasting every 30s showing real working status - Replace constant capability broadcasts with change-based system - Implement persistent capability storage in ~/.config/bzzz/ - Add SimpleTaskTracker for real task status monitoring - Only broadcast capabilities on startup or when models/capabilities change - Add proper Hive API URL configuration and integration - Fix capability change detection with proper comparison logic This eliminates P2P mesh spam and provides accurate node availability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
229
pkg/hive/client.go
Normal file
229
pkg/hive/client.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package hive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HiveClient provides integration with the Hive task coordination system
|
||||
type HiveClient struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewHiveClient creates a new Hive API client
|
||||
func NewHiveClient(baseURL, apiKey string) *HiveClient {
|
||||
return &HiveClient{
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Repository represents a Git repository configuration from Hive
|
||||
type Repository struct {
|
||||
ProjectID int `json:"project_id"`
|
||||
Name string `json:"name"`
|
||||
GitURL string `json:"git_url"`
|
||||
Owner string `json:"owner"`
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch"`
|
||||
BzzzEnabled bool `json:"bzzz_enabled"`
|
||||
ReadyToClaim bool `json:"ready_to_claim"`
|
||||
PrivateRepo bool `json:"private_repo"`
|
||||
GitHubTokenRequired bool `json:"github_token_required"`
|
||||
}
|
||||
|
||||
// ActiveRepositoriesResponse represents the response from /api/bzzz/active-repos
|
||||
type ActiveRepositoriesResponse struct {
|
||||
Repositories []Repository `json:"repositories"`
|
||||
}
|
||||
|
||||
// TaskClaimRequest represents a task claim request to Hive
|
||||
type TaskClaimRequest struct {
|
||||
TaskID int `json:"task_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
ClaimedAt int64 `json:"claimed_at"`
|
||||
}
|
||||
|
||||
// TaskStatusUpdate represents a task status update to Hive
|
||||
type TaskStatusUpdate struct {
|
||||
Status string `json:"status"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
Results map[string]interface{} `json:"results,omitempty"`
|
||||
}
|
||||
|
||||
// GetActiveRepositories fetches all repositories marked for Bzzz consumption
|
||||
func (c *HiveClient) GetActiveRepositories(ctx context.Context) ([]Repository, error) {
|
||||
url := fmt.Sprintf("%s/api/bzzz/active-repos", c.BaseURL)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Add authentication if API key is provided
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var response ActiveRepositoriesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return response.Repositories, nil
|
||||
}
|
||||
|
||||
// GetProjectTasks fetches bzzz-task labeled issues for a specific project
|
||||
func (c *HiveClient) GetProjectTasks(ctx context.Context, projectID int) ([]map[string]interface{}, error) {
|
||||
url := fmt.Sprintf("%s/api/bzzz/projects/%d/tasks", c.BaseURL, projectID)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var tasks []map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
// ClaimTask registers a task claim with the Hive system
|
||||
func (c *HiveClient) ClaimTask(ctx context.Context, projectID, taskID int, agentID string) error {
|
||||
url := fmt.Sprintf("%s/api/bzzz/projects/%d/claim", c.BaseURL, projectID)
|
||||
|
||||
claimRequest := TaskClaimRequest{
|
||||
TaskID: taskID,
|
||||
AgentID: agentID,
|
||||
ClaimedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(claimRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal claim request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("claim request failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTaskStatus updates the task status in the Hive system
|
||||
func (c *HiveClient) UpdateTaskStatus(ctx context.Context, projectID, taskID int, status string, results map[string]interface{}) error {
|
||||
url := fmt.Sprintf("%s/api/bzzz/projects/%d/status", c.BaseURL, projectID)
|
||||
|
||||
statusUpdate := TaskStatusUpdate{
|
||||
Status: status,
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
Results: results,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(statusUpdate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal status update: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("status update failed with status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HealthCheck verifies connectivity to the Hive API
|
||||
func (c *HiveClient) HealthCheck(ctx context.Context) error {
|
||||
url := fmt.Sprintf("%s/health", c.BaseURL)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create health check request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("health check request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Hive API health check failed with status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
118
pkg/hive/models.go
Normal file
118
pkg/hive/models.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package hive
|
||||
|
||||
import "time"
|
||||
|
||||
// Project represents a project managed by the Hive system
|
||||
type Project struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
GitURL string `json:"git_url"`
|
||||
Owner string `json:"owner"`
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch"`
|
||||
BzzzEnabled bool `json:"bzzz_enabled"`
|
||||
ReadyToClaim bool `json:"ready_to_claim"`
|
||||
PrivateRepo bool `json:"private_repo"`
|
||||
GitHubTokenRequired bool `json:"github_token_required"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Task represents a task (GitHub issue) from the Hive system
|
||||
type Task struct {
|
||||
ID int `json:"id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectName string `json:"project_name"`
|
||||
GitURL string `json:"git_url"`
|
||||
Owner string `json:"owner"`
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch"`
|
||||
|
||||
// GitHub issue fields
|
||||
IssueNumber int `json:"issue_number"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
State string `json:"state"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
|
||||
// Task metadata
|
||||
TaskType string `json:"task_type"`
|
||||
Priority int `json:"priority"`
|
||||
Labels []string `json:"labels"`
|
||||
Requirements []string `json:"requirements,omitempty"`
|
||||
Deliverables []string `json:"deliverables,omitempty"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
|
||||
// Timestamps
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TaskClaim represents a task claim in the Hive system
|
||||
type TaskClaim struct {
|
||||
ID int `json:"id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
TaskID int `json:"task_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Status string `json:"status"` // claimed, in_progress, completed, failed
|
||||
ClaimedAt time.Time `json:"claimed_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Results map[string]interface{} `json:"results,omitempty"`
|
||||
}
|
||||
|
||||
// ProjectActivationRequest represents a request to activate/deactivate a project
|
||||
type ProjectActivationRequest struct {
|
||||
BzzzEnabled bool `json:"bzzz_enabled"`
|
||||
ReadyToClaim bool `json:"ready_to_claim"`
|
||||
}
|
||||
|
||||
// ProjectRegistrationRequest represents a request to register a new project
|
||||
type ProjectRegistrationRequest struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
GitURL string `json:"git_url"`
|
||||
PrivateRepo bool `json:"private_repo"`
|
||||
BzzzEnabled bool `json:"bzzz_enabled"`
|
||||
AutoActivate bool `json:"auto_activate"`
|
||||
}
|
||||
|
||||
// AgentCapability represents an agent's capabilities for task matching
|
||||
type AgentCapability struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
NodeID string `json:"node_id"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Models []string `json:"models"`
|
||||
Status string `json:"status"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
}
|
||||
|
||||
// CoordinationEvent represents a P2P coordination event
|
||||
type CoordinationEvent struct {
|
||||
EventID string `json:"event_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
TaskID int `json:"task_id"`
|
||||
EventType string `json:"event_type"` // task_claimed, plan_proposed, escalated, completed
|
||||
AgentID string `json:"agent_id"`
|
||||
Message string `json:"message"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// ErrorResponse represents an error response from the Hive API
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
// HealthStatus represents the health status of the Hive system
|
||||
type HealthStatus struct {
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
Database string `json:"database"`
|
||||
Uptime string `json:"uptime"`
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
Reference in New Issue
Block a user