Fix Go module imports and add dynamic Ollama model selection with N8N integration
- Fixed module path from github.com/deepblackcloud/bzzz to github.com/anthonyrawlins/bzzz - Added dynamic Ollama model detection via /api/tags endpoint - Implemented intelligent model selection through N8N webhook integration - Added BZZZ_MODEL_SELECTION_WEBHOOK environment variable support - Fixed GitHub assignee issue by using valid username instead of peer ID - Added comprehensive model fallback mechanisms - Updated all import statements across the codebase - Removed duplicate systemd service file - Added sandbox execution environment and type definitions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -64,7 +64,7 @@ Based on comprehensive analysis of the existing Hive infrastructure and Bzzz's P
|
||||
# Docker Compose configuration for bzzz-agent
|
||||
services:
|
||||
bzzz-agent:
|
||||
image: bzzz-agent:latest
|
||||
image: registry.home.deepblack.cloud/tony/bzzz-agent:latest
|
||||
network_mode: "host" # Direct host network access for P2P
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
|
||||
23
Dockerfile.sandbox
Normal file
23
Dockerfile.sandbox
Normal file
@@ -0,0 +1,23 @@
|
||||
# Use a standard Go development image as the base
|
||||
FROM golang:1.21
|
||||
|
||||
# Install common development tools and security updates
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
git \
|
||||
curl \
|
||||
tree \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user for the agent to run as
|
||||
RUN useradd -ms /bin/bash agent
|
||||
|
||||
# Set the working directory for the agent
|
||||
WORKDIR /home/agent/work
|
||||
|
||||
# Switch to the non-root user
|
||||
USER agent
|
||||
|
||||
# Keep the container alive
|
||||
CMD ["sleep", "infinity"]
|
||||
|
||||
@@ -19,6 +19,8 @@ TimeoutStopSec=30
|
||||
# Environment variables
|
||||
Environment=HOME=/home/tony
|
||||
Environment=USER=tony
|
||||
Environment=BZZZ_HIVE_API_URL=https://hive.home.deepblack.cloud
|
||||
Environment=BZZZ_GITHUB_TOKEN_FILE=/home/tony/AI/secrets/passwords_and_tokens/gh-token
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/discovery"
|
||||
"github.com/deepblackcloud/bzzz/monitoring"
|
||||
"github.com/deepblackcloud/bzzz/p2p"
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/test"
|
||||
"github.com/anthonyrawlins/bzzz/discovery"
|
||||
"github.com/anthonyrawlins/bzzz/monitoring"
|
||||
"github.com/anthonyrawlins/bzzz/p2p"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/discovery"
|
||||
"github.com/deepblackcloud/bzzz/p2p"
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/test"
|
||||
"github.com/anthonyrawlins/bzzz/discovery"
|
||||
"github.com/anthonyrawlins/bzzz/p2p"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
106
executor/executor.go
Normal file
106
executor/executor.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anthonyrawlins/bzzz/logging"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/types"
|
||||
"github.com/anthonyrawlins/bzzz/reasoning"
|
||||
"github.com/anthonyrawlins/bzzz/sandbox"
|
||||
)
|
||||
|
||||
const maxIterations = 10 // Prevents infinite loops
|
||||
|
||||
// ExecuteTask manages the entire lifecycle of a task using a sandboxed environment.
|
||||
func ExecuteTask(ctx context.Context, task *types.EnhancedTask, hlog *logging.HypercoreLog) (string, error) {
|
||||
// 1. Create the sandbox environment
|
||||
sb, err := sandbox.CreateSandbox(ctx, "") // Use default image for now
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create sandbox: %w", err)
|
||||
}
|
||||
defer sb.DestroySandbox()
|
||||
|
||||
// 2. Clone the repository inside the sandbox
|
||||
cloneCmd := fmt.Sprintf("git clone %s .", task.GitURL)
|
||||
if _, err := sb.RunCommand(cloneCmd); err != nil {
|
||||
return "", fmt.Errorf("failed to clone repository in sandbox: %w", err)
|
||||
}
|
||||
hlog.Append(logging.TaskProgress, map[string]interface{}{"task_id": task.Number, "status": "cloned repo"})
|
||||
|
||||
// 3. The main iterative development loop
|
||||
var lastCommandOutput string
|
||||
for i := 0; i < maxIterations; i++ {
|
||||
// a. Generate the next command based on the task and previous output
|
||||
nextCommand, err := generateNextCommand(ctx, task, lastCommandOutput)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate next command: %w", err)
|
||||
}
|
||||
|
||||
hlog.Append(logging.TaskProgress, map[string]interface{}{
|
||||
"task_id": task.Number,
|
||||
"iteration": i,
|
||||
"command": nextCommand,
|
||||
})
|
||||
|
||||
// b. Check for completion command
|
||||
if strings.HasPrefix(nextCommand, "TASK_COMPLETE") {
|
||||
fmt.Println("✅ Agent has determined the task is complete.")
|
||||
break // Exit loop to proceed with PR creation
|
||||
}
|
||||
|
||||
// c. Execute the command in the sandbox
|
||||
result, err := sb.RunCommand(nextCommand)
|
||||
if err != nil {
|
||||
// Log the error and feed it back to the agent
|
||||
lastCommandOutput = fmt.Sprintf("Command failed: %v\nStdout: %s\nStderr: %s", err, result.StdOut, result.StdErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// d. Store the output for the next iteration
|
||||
lastCommandOutput = fmt.Sprintf("Stdout: %s\nStderr: %s", result.StdOut, result.StdErr)
|
||||
}
|
||||
|
||||
// 4. Create a new branch and commit the changes
|
||||
branchName := fmt.Sprintf("bzzz-task-%d", task.Number)
|
||||
if _, err := sb.RunCommand(fmt.Sprintf("git checkout -b %s", branchName)); err != nil {
|
||||
return "", fmt.Errorf("failed to create branch: %w", err)
|
||||
}
|
||||
if _, err := sb.RunCommand("git add ."); err != nil {
|
||||
return "", fmt.Errorf("failed to add files: %w", err)
|
||||
}
|
||||
commitCmd := fmt.Sprintf("git commit -m 'feat: resolve task #%d'", task.Number)
|
||||
if _, err := sb.RunCommand(commitCmd); err != nil {
|
||||
return "", fmt.Errorf("failed to commit changes: %w", err)
|
||||
}
|
||||
|
||||
// 5. Push the new branch
|
||||
if _, err := sb.RunCommand(fmt.Sprintf("git push origin %s", branchName)); err != nil {
|
||||
return "", fmt.Errorf("failed to push branch: %w", err)
|
||||
}
|
||||
|
||||
hlog.Append(logging.TaskProgress, map[string]interface{}{"task_id": task.Number, "status": "pushed changes"})
|
||||
return branchName, nil
|
||||
}
|
||||
|
||||
// generateNextCommand uses the LLM to decide the next command to execute.
|
||||
func generateNextCommand(ctx context.Context, task *types.EnhancedTask, lastOutput string) (string, error) {
|
||||
prompt := fmt.Sprintf(
|
||||
"You are an AI developer in a sandboxed shell environment. Your goal is to solve the following GitHub issue:\n\n"+
|
||||
"Title: %s\nDescription: %s\n\n"+
|
||||
"You can only interact with the system by issuing shell commands. "+
|
||||
"The previous command output was:\n---\n%s\n---\n"+
|
||||
"Based on this, what is the single next shell command you should run? "+
|
||||
"If you believe the task is complete and ready for a pull request, respond with 'TASK_COMPLETE'.",
|
||||
task.Title, task.Description, lastOutput,
|
||||
)
|
||||
|
||||
// Using the main reasoning engine to generate the command
|
||||
command, err := reasoning.GenerateResponse(ctx, "phi3", prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(command), nil
|
||||
}
|
||||
@@ -156,8 +156,10 @@ func (c *Client) ClaimTask(issueNumber int, agentID string) (*Task, error) {
|
||||
}
|
||||
|
||||
// Attempt atomic assignment using GitHub's native assignment
|
||||
// GitHub only accepts existing usernames, so we'll assign to the repo owner
|
||||
githubAssignee := "anthonyrawlins"
|
||||
issueRequest := &github.IssueRequest{
|
||||
Assignee: &agentID,
|
||||
Assignee: &githubAssignee,
|
||||
}
|
||||
|
||||
// Add in-progress label
|
||||
@@ -180,6 +182,23 @@ func (c *Client) ClaimTask(issueNumber int, agentID string) (*Task, error) {
|
||||
return nil, fmt.Errorf("failed to claim task: %w", err)
|
||||
}
|
||||
|
||||
// Add a comment to track which Bzzz agent claimed this task
|
||||
claimComment := fmt.Sprintf("🐝 **Task claimed by Bzzz agent:** `%s`\n\nThis task has been automatically claimed by the Bzzz P2P task coordination system.", agentID)
|
||||
commentRequest := &github.IssueComment{
|
||||
Body: &claimComment,
|
||||
}
|
||||
_, _, err = c.client.Issues.CreateComment(
|
||||
c.ctx,
|
||||
c.config.Owner,
|
||||
c.config.Repository,
|
||||
issueNumber,
|
||||
commentRequest,
|
||||
)
|
||||
if err != nil {
|
||||
// Log error but don't fail the claim
|
||||
fmt.Printf("⚠️ Failed to add claim comment: %v\n", err)
|
||||
}
|
||||
|
||||
// Create a task branch
|
||||
if err := c.createTaskBranch(issueNumber, agentID); err != nil {
|
||||
// Log error but don't fail the claim
|
||||
@@ -315,6 +334,28 @@ func (c *Client) createTaskBranch(issueNumber int, agentID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePullRequest creates a new pull request for a completed task.
|
||||
func (c *Client) CreatePullRequest(issueNumber int, branchName, agentID string) (*github.PullRequest, error) {
|
||||
title := fmt.Sprintf("fix: resolve issue #%d via bzzz agent %s", issueNumber, agentID)
|
||||
body := fmt.Sprintf("This pull request resolves issue #%d, and was automatically generated by the Bzzz agent `%s`.", issueNumber, agentID)
|
||||
head := branchName
|
||||
base := c.config.BaseBranch
|
||||
|
||||
pr := &github.NewPullRequest{
|
||||
Title: &title,
|
||||
Body: &body,
|
||||
Head: &head,
|
||||
Base: &base,
|
||||
}
|
||||
|
||||
newPR, _, err := c.client.PullRequests.Create(c.ctx, c.config.Owner, c.config.Repository, pr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create pull request: %w", err)
|
||||
}
|
||||
|
||||
return newPR, nil
|
||||
}
|
||||
|
||||
// formatTaskBody formats task details into GitHub issue body
|
||||
func (c *Client) formatTaskBody(task *Task) string {
|
||||
body := fmt.Sprintf("**Task Type:** %s\n", task.TaskType)
|
||||
|
||||
@@ -7,27 +7,31 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pkg/hive"
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/reasoning"
|
||||
"github.com/anthonyrawlins/bzzz/executor"
|
||||
"github.com/anthonyrawlins/bzzz/logging"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/hive"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/types"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/reasoning"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
// HiveIntegration handles dynamic repository discovery via Hive API
|
||||
type HiveIntegration struct {
|
||||
hiveClient *hive.HiveClient
|
||||
githubToken string
|
||||
pubsub *pubsub.PubSub
|
||||
ctx context.Context
|
||||
config *IntegrationConfig
|
||||
hiveClient *hive.HiveClient
|
||||
githubToken string
|
||||
pubsub *pubsub.PubSub
|
||||
hlog *logging.HypercoreLog
|
||||
ctx context.Context
|
||||
config *IntegrationConfig
|
||||
|
||||
// Repository management
|
||||
repositories map[int]*RepositoryClient // projectID -> GitHub client
|
||||
repositoryLock sync.RWMutex
|
||||
repositories map[int]*RepositoryClient // projectID -> GitHub client
|
||||
repositoryLock sync.RWMutex
|
||||
|
||||
// Conversation tracking
|
||||
activeDiscussions map[string]*Conversation // "projectID:taskID" -> conversation
|
||||
discussionLock sync.RWMutex
|
||||
discussionLock sync.RWMutex
|
||||
}
|
||||
|
||||
// RepositoryClient wraps a GitHub client for a specific repository
|
||||
@@ -38,7 +42,7 @@ type RepositoryClient struct {
|
||||
}
|
||||
|
||||
// NewHiveIntegration creates a new Hive-based GitHub integration
|
||||
func NewHiveIntegration(ctx context.Context, hiveClient *hive.HiveClient, githubToken string, ps *pubsub.PubSub, config *IntegrationConfig) *HiveIntegration {
|
||||
func NewHiveIntegration(ctx context.Context, hiveClient *hive.HiveClient, githubToken string, ps *pubsub.PubSub, hlog *logging.HypercoreLog, config *IntegrationConfig) *HiveIntegration {
|
||||
if config.PollInterval == 0 {
|
||||
config.PollInterval = 30 * time.Second
|
||||
}
|
||||
@@ -50,6 +54,7 @@ func NewHiveIntegration(ctx context.Context, hiveClient *hive.HiveClient, github
|
||||
hiveClient: hiveClient,
|
||||
githubToken: githubToken,
|
||||
pubsub: ps,
|
||||
hlog: hlog,
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
repositories: make(map[int]*RepositoryClient),
|
||||
@@ -170,7 +175,7 @@ func (hi *HiveIntegration) pollAllRepositories() {
|
||||
|
||||
fmt.Printf("🔍 Polling %d repositories for available tasks...\n", len(repositories))
|
||||
|
||||
var allTasks []*EnhancedTask
|
||||
var allTasks []*types.EnhancedTask
|
||||
|
||||
// Collect tasks from all repositories
|
||||
for _, repoClient := range repositories {
|
||||
@@ -202,7 +207,7 @@ func (hi *HiveIntegration) pollAllRepositories() {
|
||||
}
|
||||
|
||||
// getRepositoryTasks fetches available tasks from a specific repository
|
||||
func (hi *HiveIntegration) getRepositoryTasks(repoClient *RepositoryClient) ([]*EnhancedTask, error) {
|
||||
func (hi *HiveIntegration) getRepositoryTasks(repoClient *RepositoryClient) ([]*types.EnhancedTask, error) {
|
||||
// Get tasks from GitHub
|
||||
githubTasks, err := repoClient.Client.ListAvailableTasks()
|
||||
if err != nil {
|
||||
@@ -210,10 +215,23 @@ func (hi *HiveIntegration) getRepositoryTasks(repoClient *RepositoryClient) ([]*
|
||||
}
|
||||
|
||||
// Convert to enhanced tasks with project context
|
||||
var enhancedTasks []*EnhancedTask
|
||||
var enhancedTasks []*types.EnhancedTask
|
||||
for _, task := range githubTasks {
|
||||
enhancedTask := &EnhancedTask{
|
||||
Task: *task,
|
||||
enhancedTask := &types.EnhancedTask{
|
||||
ID: task.ID,
|
||||
Number: task.Number,
|
||||
Title: task.Title,
|
||||
Description: task.Description,
|
||||
State: task.State,
|
||||
Labels: task.Labels,
|
||||
Assignee: task.Assignee,
|
||||
CreatedAt: task.CreatedAt,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
TaskType: task.TaskType,
|
||||
Priority: task.Priority,
|
||||
Requirements: task.Requirements,
|
||||
Deliverables: task.Deliverables,
|
||||
Context: task.Context,
|
||||
ProjectID: repoClient.Repository.ProjectID,
|
||||
GitURL: repoClient.Repository.GitURL,
|
||||
Repository: repoClient.Repository,
|
||||
@@ -221,20 +239,12 @@ func (hi *HiveIntegration) getRepositoryTasks(repoClient *RepositoryClient) ([]*
|
||||
enhancedTasks = append(enhancedTasks, enhancedTask)
|
||||
}
|
||||
|
||||
return enhancedTasks, nil
|
||||
}
|
||||
|
||||
// EnhancedTask extends Task with project context
|
||||
type EnhancedTask struct {
|
||||
Task
|
||||
ProjectID int
|
||||
GitURL string
|
||||
Repository hive.Repository
|
||||
return enhancedTasks, nil
|
||||
}
|
||||
|
||||
// filterSuitableTasks filters tasks based on agent capabilities
|
||||
func (hi *HiveIntegration) filterSuitableTasks(tasks []*EnhancedTask) []*EnhancedTask {
|
||||
var suitable []*EnhancedTask
|
||||
func (hi *HiveIntegration) filterSuitableTasks(tasks []*types.EnhancedTask) []*types.EnhancedTask {
|
||||
var suitable []*types.EnhancedTask
|
||||
|
||||
for _, task := range tasks {
|
||||
if hi.canHandleTaskType(task.TaskType) {
|
||||
@@ -256,7 +266,7 @@ func (hi *HiveIntegration) canHandleTaskType(taskType string) bool {
|
||||
}
|
||||
|
||||
// claimAndExecuteTask claims a task and begins execution
|
||||
func (hi *HiveIntegration) claimAndExecuteTask(task *EnhancedTask) {
|
||||
func (hi *HiveIntegration) claimAndExecuteTask(task *types.EnhancedTask) {
|
||||
hi.repositoryLock.RLock()
|
||||
repoClient, exists := hi.repositories[task.ProjectID]
|
||||
hi.repositoryLock.RUnlock()
|
||||
@@ -267,7 +277,7 @@ func (hi *HiveIntegration) claimAndExecuteTask(task *EnhancedTask) {
|
||||
}
|
||||
|
||||
// Claim the task in GitHub
|
||||
claimedTask, err := repoClient.Client.ClaimTask(task.Number, hi.config.AgentID)
|
||||
_, err := repoClient.Client.ClaimTask(task.Number, hi.config.AgentID)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to claim task %d in %s/%s: %v\n",
|
||||
task.Number, task.Repository.Owner, task.Repository.Repository, err)
|
||||
@@ -275,7 +285,14 @@ func (hi *HiveIntegration) claimAndExecuteTask(task *EnhancedTask) {
|
||||
}
|
||||
|
||||
fmt.Printf("✋ Claimed task #%d from %s/%s: %s\n",
|
||||
claimedTask.Number, task.Repository.Owner, task.Repository.Repository, claimedTask.Title)
|
||||
task.Number, task.Repository.Owner, task.Repository.Repository, task.Title)
|
||||
|
||||
// Log the claim
|
||||
hi.hlog.Append(logging.TaskClaimed, map[string]interface{}{
|
||||
"task_id": task.Number,
|
||||
"repository": fmt.Sprintf("%s/%s", task.Repository.Owner, task.Repository.Repository),
|
||||
"title": task.Title,
|
||||
})
|
||||
|
||||
// Report claim to Hive
|
||||
if err := hi.hiveClient.ClaimTask(hi.ctx, task.ProjectID, task.Number, hi.config.AgentID); err != nil {
|
||||
@@ -288,113 +305,113 @@ func (hi *HiveIntegration) claimAndExecuteTask(task *EnhancedTask) {
|
||||
|
||||
// executeTask executes a claimed task with reasoning and coordination
|
||||
func (hi *HiveIntegration) executeTask(task *EnhancedTask, repoClient *RepositoryClient) {
|
||||
fmt.Printf("🚀 Starting execution of task #%d from %s/%s: %s\n",
|
||||
task.Number, task.Repository.Owner, task.Repository.Repository, task.Title)
|
||||
// Define the dynamic topic for this task
|
||||
taskTopic := fmt.Sprintf("bzzz/meta/issue/%d", task.Number)
|
||||
hi.pubsub.JoinDynamicTopic(taskTopic)
|
||||
defer hi.pubsub.LeaveDynamicTopic(taskTopic)
|
||||
|
||||
// Generate execution plan using reasoning
|
||||
prompt := fmt.Sprintf("You are an expert AI developer working on a distributed task from repository %s/%s. "+
|
||||
"Create a concise, step-by-step plan to resolve this GitHub issue. "+
|
||||
"Issue Title: %s. Issue Body: %s. Project Context: %s",
|
||||
task.Repository.Owner, task.Repository.Repository, task.Title, task.Description, task.GitURL)
|
||||
fmt.Printf("🚀 Starting execution of task #%d in sandbox...\n", task.Number)
|
||||
|
||||
plan, err := reasoning.GenerateResponse(hi.ctx, "phi3", prompt)
|
||||
// The executor now handles the entire iterative process.
|
||||
branchName, err := executor.ExecuteTask(hi.ctx, task, hi.hlog)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to generate execution plan for task #%d: %v\n", task.Number, err)
|
||||
fmt.Printf("❌ Failed to execute task #%d: %v\n", task.Number, err)
|
||||
hi.hlog.Append(logging.TaskFailed, map[string]interface{}{"task_id": task.Number, "reason": "task execution failed in sandbox"})
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("📝 Generated Plan for task #%d:\n%s\n", task.Number, plan)
|
||||
|
||||
// Start meta-discussion
|
||||
conversationKey := fmt.Sprintf("%d:%d", task.ProjectID, task.Number)
|
||||
|
||||
hi.discussionLock.Lock()
|
||||
hi.activeDiscussions[conversationKey] = &Conversation{
|
||||
TaskID: task.Number,
|
||||
TaskTitle: task.Title,
|
||||
TaskDescription: task.Description,
|
||||
History: []string{fmt.Sprintf("Plan by %s (%s/%s): %s", hi.config.AgentID, task.Repository.Owner, task.Repository.Repository, plan)},
|
||||
LastUpdated: time.Now(),
|
||||
}
|
||||
hi.discussionLock.Unlock()
|
||||
|
||||
// Announce plan for peer review
|
||||
metaMsg := map[string]interface{}{
|
||||
"project_id": task.ProjectID,
|
||||
"issue_id": task.Number,
|
||||
"repository": fmt.Sprintf("%s/%s", task.Repository.Owner, task.Repository.Repository),
|
||||
"message": "Here is my proposed plan for this cross-repository task. What are your thoughts?",
|
||||
"plan": plan,
|
||||
"git_url": task.GitURL,
|
||||
// Create a pull request
|
||||
pr, err := repoClient.Client.CreatePullRequest(task.Number, branchName, hi.config.AgentID)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to create pull request for task #%d: %v\n", task.Number, err)
|
||||
hi.hlog.Append(logging.TaskFailed, map[string]interface{}{"task_id": task.Number, "reason": "failed to create pull request"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := hi.pubsub.PublishAntennaeMessage(pubsub.MetaDiscussion, metaMsg); err != nil {
|
||||
fmt.Printf("⚠️ Failed to publish plan to meta-discussion channel: %v\n", err)
|
||||
fmt.Printf("✅ Successfully created pull request for task #%d: %s\n", task.Number, pr.GetHTMLURL())
|
||||
hi.hlog.Append(logging.TaskCompleted, map[string]interface{}{
|
||||
"task_id": task.Number,
|
||||
"pr_url": pr.GetHTMLURL(),
|
||||
"pr_number": pr.GetNumber(),
|
||||
})
|
||||
|
||||
// Report completion to Hive
|
||||
if err := hi.hiveClient.UpdateTaskStatus(hi.ctx, task.ProjectID, task.Number, "completed", map[string]interface{}{
|
||||
"pull_request_url": pr.GetHTMLURL(),
|
||||
}); err != nil {
|
||||
fmt.Printf("⚠️ Failed to report task completion to Hive: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMetaDiscussion handles incoming meta-discussion messages
|
||||
// requestAssistance publishes a help request to the task-specific topic.
|
||||
func (hi *HiveIntegration) requestAssistance(task *EnhancedTask, reason, topic string) {
|
||||
fmt.Printf("🆘 Agent %s is requesting assistance for task #%d: %s\n", hi.config.AgentID, task.Number, reason)
|
||||
hi.hlog.Append(logging.TaskHelpRequested, map[string]interface{}{
|
||||
"task_id": task.Number,
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
helpRequest := map[string]interface{}{
|
||||
"issue_id": task.Number,
|
||||
"repository": fmt.Sprintf("%s/%s", task.Repository.Owner, task.Repository.Repository),
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
hi.pubsub.PublishToDynamicTopic(topic, pubsub.TaskHelpRequest, helpRequest)
|
||||
}
|
||||
|
||||
// handleMetaDiscussion handles all incoming messages from dynamic and static topics.
|
||||
func (hi *HiveIntegration) handleMetaDiscussion(msg pubsub.Message, from peer.ID) {
|
||||
projectID, hasProject := msg.Data["project_id"].(float64)
|
||||
issueID, hasIssue := msg.Data["issue_id"].(float64)
|
||||
|
||||
if !hasProject || !hasIssue {
|
||||
return
|
||||
switch msg.Type {
|
||||
case pubsub.TaskHelpRequest:
|
||||
hi.handleHelpRequest(msg, from)
|
||||
case pubsub.TaskHelpResponse:
|
||||
hi.handleHelpResponse(msg, from)
|
||||
default:
|
||||
// Handle other meta-discussion messages (e.g., peer feedback)
|
||||
}
|
||||
}
|
||||
|
||||
conversationKey := fmt.Sprintf("%d:%d", int(projectID), int(issueID))
|
||||
// handleHelpRequest is called when another agent requests assistance.
|
||||
func (hi *HiveIntegration) handleHelpRequest(msg pubsub.Message, from peer.ID) {
|
||||
issueID, _ := msg.Data["issue_id"].(float64)
|
||||
reason, _ := msg.Data["reason"].(string)
|
||||
fmt.Printf("🙋 Received help request for task #%d from %s: %s\n", int(issueID), from.ShortString(), reason)
|
||||
|
||||
hi.discussionLock.Lock()
|
||||
convo, exists := hi.activeDiscussions[conversationKey]
|
||||
if !exists || convo.IsEscalated {
|
||||
hi.discussionLock.Unlock()
|
||||
return
|
||||
// Simple logic: if we are not busy, we can help.
|
||||
// A more advanced agent would check its capabilities against the reason.
|
||||
canHelp := true // Placeholder for more complex logic
|
||||
|
||||
if canHelp {
|
||||
fmt.Printf("✅ Agent %s can help with task #%d\n", hi.config.AgentID, int(issueID))
|
||||
hi.hlog.Append(logging.TaskHelpOffered, map[string]interface{}{
|
||||
"task_id": int(issueID),
|
||||
"requester_id": from.ShortString(),
|
||||
})
|
||||
|
||||
response := map[string]interface{}{
|
||||
"issue_id": issueID,
|
||||
"can_help": true,
|
||||
"capabilities": hi.config.Capabilities,
|
||||
}
|
||||
taskTopic := fmt.Sprintf("bzzz/meta/issue/%d", int(issueID))
|
||||
hi.pubsub.PublishToDynamicTopic(taskTopic, pubsub.TaskHelpResponse, response)
|
||||
}
|
||||
}
|
||||
|
||||
incomingMessage, _ := msg.Data["message"].(string)
|
||||
repository, _ := msg.Data["repository"].(string)
|
||||
// handleHelpResponse is called when an agent receives an offer for help.
|
||||
func (hi *HiveIntegration) handleHelpResponse(msg pubsub.Message, from peer.ID) {
|
||||
issueID, _ := msg.Data["issue_id"].(float64)
|
||||
canHelp, _ := msg.Data["can_help"].(bool)
|
||||
|
||||
convo.History = append(convo.History, fmt.Sprintf("Response from %s (%s): %s", from.ShortString(), repository, incomingMessage))
|
||||
convo.LastUpdated = time.Now()
|
||||
hi.discussionLock.Unlock()
|
||||
|
||||
fmt.Printf("🎯 Received peer feedback for task #%d in project %d. Generating response...\n", int(issueID), int(projectID))
|
||||
|
||||
// Generate intelligent response
|
||||
historyStr := strings.Join(convo.History, "\n")
|
||||
prompt := fmt.Sprintf(
|
||||
"You are an AI developer collaborating on a distributed task across multiple repositories. "+
|
||||
"Repository: %s. Task: %s. Description: %s. "+
|
||||
"Conversation history:\n%s\n\n"+
|
||||
"Based on the last message, provide a concise and helpful response for cross-repository coordination.",
|
||||
repository, convo.TaskTitle, convo.TaskDescription, historyStr,
|
||||
)
|
||||
|
||||
response, err := reasoning.GenerateResponse(hi.ctx, "phi3", prompt)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Failed to generate response for task #%d: %v\n", int(issueID), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for escalation
|
||||
if hi.shouldEscalate(response, convo.History) {
|
||||
fmt.Printf("🚨 Escalating task #%d in project %d for human review.\n", int(issueID), int(projectID))
|
||||
convo.IsEscalated = true
|
||||
go hi.triggerHumanEscalation(int(projectID), convo, response)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("💬 Sending response for task #%d in project %d...\n", int(issueID), int(projectID))
|
||||
|
||||
responseMsg := map[string]interface{}{
|
||||
"project_id": int(projectID),
|
||||
"issue_id": int(issueID),
|
||||
"repository": repository,
|
||||
"message": response,
|
||||
}
|
||||
|
||||
if err := hi.pubsub.PublishAntennaeMessage(pubsub.MetaDiscussion, responseMsg); err != nil {
|
||||
fmt.Printf("⚠️ Failed to publish response: %v\n", err)
|
||||
if canHelp {
|
||||
fmt.Printf("🤝 Received help offer for task #%d from %s\n", int(issueID), from.ShortString())
|
||||
hi.hlog.Append(logging.TaskHelpReceived, map[string]interface{}{
|
||||
"task_id": int(issueID),
|
||||
"helper_id": from.ShortString(),
|
||||
})
|
||||
// In a full implementation, the agent would now delegate a sub-task
|
||||
// or use the helper's capabilities. For now, we just log it.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,6 +437,11 @@ func (hi *HiveIntegration) shouldEscalate(response string, history []string) boo
|
||||
|
||||
// triggerHumanEscalation sends escalation to Hive and N8N
|
||||
func (hi *HiveIntegration) triggerHumanEscalation(projectID int, convo *Conversation, reason string) {
|
||||
hi.hlog.Append(logging.Escalation, map[string]interface{}{
|
||||
"task_id": convo.TaskID,
|
||||
"reason": reason,
|
||||
})
|
||||
|
||||
// Report to Hive system
|
||||
if err := hi.hiveClient.UpdateTaskStatus(hi.ctx, projectID, convo.TaskID, "escalated", map[string]interface{}{
|
||||
"escalation_reason": reason,
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/reasoning"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/reasoning"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
|
||||
27
go.mod
27
go.mod
@@ -1,6 +1,8 @@
|
||||
module github.com/deepblackcloud/bzzz
|
||||
module github.com/anthonyrawlins/bzzz
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
github.com/google/go-github/v57 v57.0.0
|
||||
@@ -12,17 +14,26 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.3.2+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/elastic/gosigar v0.14.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/flynn/noise v1.0.0 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -57,6 +68,7 @@ require (
|
||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
||||
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
@@ -68,6 +80,8 @@ require (
|
||||
github.com/multiformats/go-multistream v0.5.0 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@@ -81,6 +95,11 @@ require (
|
||||
github.com/quic-go/webtransport-go v0.6.0 // indirect
|
||||
github.com/raulk/go-watchdog v1.3.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/dig v1.17.1 // indirect
|
||||
go.uber.org/fx v1.20.1 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
@@ -91,10 +110,10 @@ require (
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
)
|
||||
|
||||
42
go.sum
42
go.sum
@@ -40,6 +40,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -70,6 +72,10 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
||||
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
@@ -85,6 +91,12 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5il
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
|
||||
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
@@ -96,6 +108,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
|
||||
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||
@@ -116,8 +130,13 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
@@ -173,6 +192,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs=
|
||||
github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw=
|
||||
@@ -301,6 +321,8 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8Rv
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
@@ -341,6 +363,10 @@ github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
|
||||
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
@@ -420,6 +446,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
@@ -436,6 +463,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
@@ -451,6 +479,16 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
@@ -642,6 +680,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -800,6 +840,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -48,11 +48,14 @@ const (
|
||||
TaskFailed LogType = "task_failed"
|
||||
|
||||
// Antennae meta-discussion logs
|
||||
PlanProposed LogType = "plan_proposed"
|
||||
ObjectionRaised LogType = "objection_raised"
|
||||
Collaboration LogType = "collaboration"
|
||||
ConsensusReached LogType = "consensus_reached"
|
||||
Escalation LogType = "escalation"
|
||||
PlanProposed LogType = "plan_proposed"
|
||||
ObjectionRaised LogType = "objection_raised"
|
||||
Collaboration LogType = "collaboration"
|
||||
ConsensusReached LogType = "consensus_reached"
|
||||
Escalation LogType = "escalation"
|
||||
TaskHelpRequested LogType = "task_help_requested"
|
||||
TaskHelpOffered LogType = "task_help_offered"
|
||||
TaskHelpReceived LogType = "task_help_received"
|
||||
|
||||
// System logs
|
||||
PeerJoined LogType = "peer_joined"
|
||||
|
||||
139
main.go
139
main.go
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -12,12 +14,14 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/discovery"
|
||||
"github.com/deepblackcloud/bzzz/github"
|
||||
"github.com/deepblackcloud/bzzz/p2p"
|
||||
"github.com/deepblackcloud/bzzz/pkg/config"
|
||||
"github.com/deepblackcloud/bzzz/pkg/hive"
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/discovery"
|
||||
"github.com/anthonyrawlins/bzzz/github"
|
||||
"github.com/anthonyrawlins/bzzz/logging"
|
||||
"github.com/anthonyrawlins/bzzz/p2p"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/config"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/hive"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/reasoning"
|
||||
)
|
||||
|
||||
// SimpleTaskTracker tracks active tasks for availability reporting
|
||||
@@ -97,6 +101,11 @@ func main() {
|
||||
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 {
|
||||
@@ -147,7 +156,7 @@ func main() {
|
||||
MaxTasks: cfg.Agent.MaxTasks,
|
||||
}
|
||||
|
||||
ghIntegration = github.NewHiveIntegration(ctx, hiveClient, githubToken, ps, integrationConfig)
|
||||
ghIntegration = github.NewHiveIntegration(ctx, hiveClient, githubToken, ps, hlog, integrationConfig)
|
||||
|
||||
// Start the integration service
|
||||
ghIntegration.Start()
|
||||
@@ -215,8 +224,124 @@ func announceAvailability(ps *pubsub.PubSub, nodeID string, taskTracker *SimpleT
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Get current capabilities
|
||||
currentCaps := map[string]interface{}{
|
||||
"node_id": nodeID,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
)
|
||||
|
||||
// AntennaeMonitor tracks and logs antennae coordination activity
|
||||
|
||||
@@ -29,12 +29,13 @@ type HiveAPIConfig struct {
|
||||
|
||||
// AgentConfig holds agent-specific configuration
|
||||
type AgentConfig struct {
|
||||
ID string `yaml:"id"`
|
||||
Capabilities []string `yaml:"capabilities"`
|
||||
PollInterval time.Duration `yaml:"poll_interval"`
|
||||
MaxTasks int `yaml:"max_tasks"`
|
||||
Models []string `yaml:"models"`
|
||||
Specialization string `yaml:"specialization"`
|
||||
ID string `yaml:"id"`
|
||||
Capabilities []string `yaml:"capabilities"`
|
||||
PollInterval time.Duration `yaml:"poll_interval"`
|
||||
MaxTasks int `yaml:"max_tasks"`
|
||||
Models []string `yaml:"models"`
|
||||
Specialization string `yaml:"specialization"`
|
||||
ModelSelectionWebhook string `yaml:"model_selection_webhook"`
|
||||
}
|
||||
|
||||
// GitHubConfig holds GitHub integration settings
|
||||
@@ -100,11 +101,12 @@ func getDefaultConfig() *Config {
|
||||
RetryCount: 3,
|
||||
},
|
||||
Agent: AgentConfig{
|
||||
Capabilities: []string{"general", "reasoning", "task-coordination"},
|
||||
PollInterval: 30 * time.Second,
|
||||
MaxTasks: 3,
|
||||
Models: []string{"phi3", "llama3.1"},
|
||||
Specialization: "general_developer",
|
||||
Capabilities: []string{"general", "reasoning", "task-coordination"},
|
||||
PollInterval: 30 * time.Second,
|
||||
MaxTasks: 3,
|
||||
Models: []string{"phi3", "llama3.1"},
|
||||
Specialization: "general_developer",
|
||||
ModelSelectionWebhook: "https://n8n.home.deepblack.cloud/webhook/model-selection",
|
||||
},
|
||||
GitHub: GitHubConfig{
|
||||
TokenFile: "/home/tony/AI/secrets/passwords_and_tokens/gh-token",
|
||||
@@ -164,6 +166,9 @@ func loadFromEnv(config *Config) error {
|
||||
if specialization := os.Getenv("BZZZ_AGENT_SPECIALIZATION"); specialization != "" {
|
||||
config.Agent.Specialization = specialization
|
||||
}
|
||||
if modelWebhook := os.Getenv("BZZZ_MODEL_SELECTION_WEBHOOK"); modelWebhook != "" {
|
||||
config.Agent.ModelSelectionWebhook = modelWebhook
|
||||
}
|
||||
|
||||
// GitHub configuration
|
||||
if tokenFile := os.Getenv("BZZZ_GITHUB_TOKEN_FILE"); tokenFile != "" {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/reasoning"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/reasoning"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
)
|
||||
|
||||
|
||||
35
pkg/types/task.go
Normal file
35
pkg/types/task.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/anthonyrawlins/bzzz/pkg/hive"
|
||||
)
|
||||
|
||||
// EnhancedTask extends a basic Task with project-specific context.
|
||||
// It's the primary data structure passed between the github, executor,
|
||||
// and reasoning components.
|
||||
type EnhancedTask struct {
|
||||
// Core task details, originally from the GitHub issue.
|
||||
ID int64
|
||||
Number int
|
||||
Title string
|
||||
Description string
|
||||
State string
|
||||
Labels []string
|
||||
Assignee string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
|
||||
// Bzzz-specific fields parsed from the issue body or labels.
|
||||
TaskType string
|
||||
Priority int
|
||||
Requirements []string
|
||||
Deliverables []string
|
||||
Context map[string]interface{}
|
||||
|
||||
// Hive-integration fields providing repository context.
|
||||
ProjectID int
|
||||
GitURL string
|
||||
Repository hive.Repository
|
||||
}
|
||||
143
pubsub/pubsub.go
143
pubsub/pubsub.go
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
@@ -26,6 +27,12 @@ type PubSub struct {
|
||||
bzzzSub *pubsub.Subscription
|
||||
antennaeSub *pubsub.Subscription
|
||||
|
||||
// Dynamic topic management
|
||||
dynamicTopics map[string]*pubsub.Topic
|
||||
dynamicTopicsMux sync.RWMutex
|
||||
dynamicSubs map[string]*pubsub.Subscription
|
||||
dynamicSubsMux sync.RWMutex
|
||||
|
||||
// Configuration
|
||||
bzzzTopicName string
|
||||
antennaeTopicName string
|
||||
@@ -48,6 +55,8 @@ const (
|
||||
|
||||
// Antennae 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
|
||||
@@ -93,10 +102,12 @@ func NewPubSub(ctx context.Context, h host.Host, bzzzTopic, antennaeTopic string
|
||||
cancel: cancel,
|
||||
bzzzTopicName: bzzzTopic,
|
||||
antennaeTopicName: antennaeTopic,
|
||||
dynamicTopics: make(map[string]*pubsub.Topic),
|
||||
dynamicSubs: make(map[string]*pubsub.Subscription),
|
||||
}
|
||||
|
||||
// Join topics
|
||||
if err := p.joinTopics(); err != nil {
|
||||
// Join static topics
|
||||
if err := p.joinStaticTopics(); err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,13 +121,12 @@ func NewPubSub(ctx context.Context, h host.Host, bzzzTopic, antennaeTopic string
|
||||
}
|
||||
|
||||
// SetAntennaeMessageHandler sets the handler for incoming Antennae messages.
|
||||
// This allows the business logic (e.g., in the github module) to process messages.
|
||||
func (p *PubSub) SetAntennaeMessageHandler(handler func(msg Message, from peer.ID)) {
|
||||
p.AntennaeMessageHandler = handler
|
||||
}
|
||||
|
||||
// joinTopics joins the Bzzz coordination and Antennae meta-discussion topics
|
||||
func (p *PubSub) joinTopics() error {
|
||||
// joinStaticTopics joins the main Bzzz and Antennae topics
|
||||
func (p *PubSub) joinStaticTopics() error {
|
||||
// Join Bzzz coordination topic
|
||||
bzzzTopic, err := p.ps.Join(p.bzzzTopicName)
|
||||
if err != nil {
|
||||
@@ -124,7 +134,6 @@ func (p *PubSub) joinTopics() error {
|
||||
}
|
||||
p.bzzzTopic = bzzzTopic
|
||||
|
||||
// Subscribe to Bzzz messages
|
||||
bzzzSub, err := bzzzTopic.Subscribe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to Bzzz topic: %w", err)
|
||||
@@ -138,7 +147,6 @@ func (p *PubSub) joinTopics() error {
|
||||
}
|
||||
p.antennaeTopic = antennaeTopic
|
||||
|
||||
// Subscribe to Antennae messages
|
||||
antennaeSub, err := antennaeTopic.Subscribe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to subscribe to Antennae topic: %w", err)
|
||||
@@ -148,6 +156,83 @@ func (p *PubSub) joinTopics() error {
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// PublishBzzzMessage publishes a message to the Bzzz coordination topic
|
||||
func (p *PubSub) PublishBzzzMessage(msgType MessageType, data map[string]interface{}) error {
|
||||
msg := Message{
|
||||
@@ -194,7 +279,6 @@ func (p *PubSub) handleBzzzMessages() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip our own messages
|
||||
if msg.ReceivedFrom == p.host.ID() {
|
||||
continue
|
||||
}
|
||||
@@ -221,7 +305,6 @@ func (p *PubSub) handleAntennaeMessages() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip our own messages
|
||||
if msg.ReceivedFrom == p.host.ID() {
|
||||
continue
|
||||
}
|
||||
@@ -232,16 +315,43 @@ func (p *PubSub) handleAntennaeMessages() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If an external handler is registered, use it.
|
||||
if p.AntennaeMessageHandler != nil {
|
||||
p.AntennaeMessageHandler(antennaeMsg, msg.ReceivedFrom)
|
||||
} else {
|
||||
// Default processing if no handler is set
|
||||
p.processAntennaeMessage(antennaeMsg, 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 Antennae handler for all dynamic messages
|
||||
if p.AntennaeMessageHandler != nil {
|
||||
p.AntennaeMessageHandler(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)
|
||||
@@ -253,11 +363,6 @@ func (p *PubSub) processAntennaeMessage(msg Message, from peer.ID) {
|
||||
msg.Type, from.ShortString(), msg.Data)
|
||||
}
|
||||
|
||||
// GetConnectedPeers returns the number of connected peers
|
||||
func (p *PubSub) GetConnectedPeers() int {
|
||||
return len(p.host.Network().Peers())
|
||||
}
|
||||
|
||||
// Close shuts down the PubSub instance
|
||||
func (p *PubSub) Close() error {
|
||||
p.cancel()
|
||||
@@ -276,5 +381,11 @@ func (p *PubSub) Close() error {
|
||||
p.antennaeTopic.Close()
|
||||
}
|
||||
|
||||
p.dynamicTopicsMux.Lock()
|
||||
for _, topic := range p.dynamicTopics {
|
||||
topic.Close()
|
||||
}
|
||||
p.dynamicTopicsMux.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ const (
|
||||
defaultTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
availableModels []string
|
||||
modelWebhookURL string
|
||||
)
|
||||
|
||||
// OllamaRequest represents the request payload for the Ollama API.
|
||||
type OllamaRequest struct {
|
||||
Model string `json:"model"`
|
||||
@@ -77,3 +82,68 @@ func GenerateResponse(ctx context.Context, model, prompt string) (string, error)
|
||||
|
||||
return ollamaResp.Response, nil
|
||||
}
|
||||
|
||||
// SetModelConfig configures the available models and webhook URL for smart model selection
|
||||
func SetModelConfig(models []string, webhookURL string) {
|
||||
availableModels = models
|
||||
modelWebhookURL = webhookURL
|
||||
}
|
||||
|
||||
// selectBestModel calls the model selection webhook to choose the best model for a prompt
|
||||
func selectBestModel(availableModels []string, prompt string) string {
|
||||
if modelWebhookURL == "" || len(availableModels) == 0 {
|
||||
// Fallback to first available model
|
||||
if len(availableModels) > 0 {
|
||||
return availableModels[0]
|
||||
}
|
||||
return "phi3" // Last resort fallback
|
||||
}
|
||||
|
||||
requestPayload := map[string]interface{}{
|
||||
"models": availableModels,
|
||||
"prompt": prompt,
|
||||
}
|
||||
|
||||
payloadBytes, err := json.Marshal(requestPayload)
|
||||
if err != nil {
|
||||
// Fallback on error
|
||||
return availableModels[0]
|
||||
}
|
||||
|
||||
resp, err := http.Post(modelWebhookURL, "application/json", bytes.NewBuffer(payloadBytes))
|
||||
if err != nil {
|
||||
// Fallback on error
|
||||
return availableModels[0]
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// Fallback on error
|
||||
return availableModels[0]
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
// Fallback on error
|
||||
return availableModels[0]
|
||||
}
|
||||
|
||||
// Validate that the returned model is in our available list
|
||||
for _, model := range availableModels {
|
||||
if model == response.Model {
|
||||
return response.Model
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if webhook returned invalid model
|
||||
return availableModels[0]
|
||||
}
|
||||
|
||||
// GenerateResponseSmart automatically selects the best model for the prompt
|
||||
func GenerateResponseSmart(ctx context.Context, prompt string) (string, error) {
|
||||
selectedModel := selectBestModel(availableModels, prompt)
|
||||
return GenerateResponse(ctx, selectedModel, prompt)
|
||||
}
|
||||
|
||||
213
sandbox/sandbox.go
Normal file
213
sandbox/sandbox.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultDockerImage is the image used if a task does not specify one.
|
||||
DefaultDockerImage = "bzzz-sandbox:latest"
|
||||
)
|
||||
|
||||
// Sandbox represents a stateful, isolated execution environment for a single task.
|
||||
type Sandbox struct {
|
||||
ID string // The ID of the running container.
|
||||
HostPath string // The path on the host machine mounted as the workspace.
|
||||
Workspace string // The path inside the container that is the workspace.
|
||||
dockerCli *client.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// CommandResult holds the output of a command executed in the sandbox.
|
||||
type CommandResult struct {
|
||||
StdOut string
|
||||
StdErr string
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
// CreateSandbox provisions a new Docker container for a task.
|
||||
func CreateSandbox(ctx context.Context, taskImage string) (*Sandbox, error) {
|
||||
if taskImage == "" {
|
||||
taskImage = DefaultDockerImage
|
||||
}
|
||||
|
||||
// Create a new Docker client
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create docker client: %w", err)
|
||||
}
|
||||
|
||||
// Create a temporary directory on the host
|
||||
hostPath, err := os.MkdirTemp("", "bzzz-sandbox-")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp dir for sandbox: %w", err)
|
||||
}
|
||||
|
||||
// Define container configuration
|
||||
containerConfig := &container.Config{
|
||||
Image: taskImage,
|
||||
Tty: true, // Keep the container running
|
||||
OpenStdin: true,
|
||||
WorkingDir: "/home/agent/work",
|
||||
User: "agent",
|
||||
}
|
||||
|
||||
// Define host configuration (e.g., volume mounts, resource limits)
|
||||
hostConfig := &container.HostConfig{
|
||||
Binds: []string{fmt.Sprintf("%s:/home/agent/work", hostPath)},
|
||||
Resources: container.Resources{
|
||||
CPUs: 2,
|
||||
Memory: 2 * 1024 * 1024 * 1024, // 2GB
|
||||
},
|
||||
}
|
||||
|
||||
// Create the container
|
||||
resp, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, nil, nil, "")
|
||||
if err != nil {
|
||||
os.RemoveAll(hostPath) // Clean up the directory if container creation fails
|
||||
return nil, fmt.Errorf("failed to create container: %w", err)
|
||||
}
|
||||
|
||||
// Start the container
|
||||
if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
|
||||
os.RemoveAll(hostPath) // Clean up
|
||||
return nil, fmt.Errorf("failed to start container: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Sandbox container %s created successfully.\n", resp.ID[:12])
|
||||
|
||||
return &Sandbox{
|
||||
ID: resp.ID,
|
||||
HostPath: hostPath,
|
||||
Workspace: "/home/agent/work",
|
||||
dockerCli: cli,
|
||||
ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DestroySandbox stops and removes the container and its associated host directory.
|
||||
func (s *Sandbox) DestroySandbox() error {
|
||||
if s == nil || s.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define a timeout for stopping the container
|
||||
timeout := 30 * time.Second
|
||||
|
||||
// Stop the container
|
||||
fmt.Printf("🛑 Stopping sandbox container %s...\n", s.ID[:12])
|
||||
err := s.dockerCli.ContainerStop(s.ctx, s.ID, container.StopOptions{Timeout: &timeout})
|
||||
if err != nil {
|
||||
// Log the error but continue to try and clean up
|
||||
fmt.Printf("⚠️ Error stopping container %s: %v. Proceeding with cleanup.\n", s.ID, err)
|
||||
}
|
||||
|
||||
// Remove the container
|
||||
err = s.dockerCli.ContainerRemove(s.ctx, s.ID, container.RemoveOptions{Force: true})
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ Error removing container %s: %v. Proceeding with cleanup.\n", s.ID, err)
|
||||
}
|
||||
|
||||
// Remove the host directory
|
||||
fmt.Printf("🗑️ Removing host directory %s...\n", s.HostPath)
|
||||
err = os.RemoveAll(s.HostPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove host directory %s: %w", s.HostPath, err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Sandbox %s destroyed successfully.\n", s.ID[:12])
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCommand executes a shell command inside the sandbox.
|
||||
func (s *Sandbox) RunCommand(command string) (*CommandResult, error) {
|
||||
// Configuration for the exec process
|
||||
execConfig := container.ExecOptions{
|
||||
Cmd: []string{"/bin/sh", "-c", command},
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Tty: false,
|
||||
}
|
||||
|
||||
// Create the exec instance
|
||||
execID, err := s.dockerCli.ContainerExecCreate(s.ctx, s.ID, execConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create exec in container: %w", err)
|
||||
}
|
||||
|
||||
// Start the exec process
|
||||
resp, err := s.dockerCli.ContainerExecAttach(s.ctx, execID.ID, container.ExecStartOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to attach to exec in container: %w", err)
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
// Read the output
|
||||
var stdout, stderr bytes.Buffer
|
||||
_, err = stdcopy.StdCopy(&stdout, &stderr, resp.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read exec output: %w", err)
|
||||
}
|
||||
|
||||
// Inspect the exec process to get the exit code
|
||||
inspect, err := s.dockerCli.ContainerExecInspect(s.ctx, execID.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to inspect exec in container: %w", err)
|
||||
}
|
||||
|
||||
return &CommandResult{
|
||||
StdOut: stdout.String(),
|
||||
StdErr: stderr.String(),
|
||||
ExitCode: inspect.ExitCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WriteFile writes content to a file inside the sandbox's workspace.
|
||||
func (s *Sandbox) WriteFile(path string, content []byte) error {
|
||||
// Create a temporary file on the host
|
||||
tmpfile, err := os.CreateTemp("", "bzzz-write-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
return fmt.Errorf("failed to write to temp file: %w", err)
|
||||
}
|
||||
tmpfile.Close()
|
||||
|
||||
// Copy the file into the container
|
||||
dstPath := filepath.Join(s.Workspace, path)
|
||||
return s.dockerCli.CopyToContainer(s.ctx, tmpfile.Name(), s.ID, dstPath)
|
||||
}
|
||||
|
||||
// ReadFile reads the content of a file from the sandbox's workspace.
|
||||
func (s *Sandbox) ReadFile(path string) ([]byte, error) {
|
||||
srcPath := filepath.Join(s.Workspace, path)
|
||||
|
||||
// Copy the file from the container
|
||||
reader, _, err := s.dockerCli.CopyFromContainer(s.ctx, s.ID, srcPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy from container: %w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// The result is a tar archive, so we need to extract it
|
||||
tr := tar.NewReader(reader)
|
||||
if _, err := tr.Next(); err != nil {
|
||||
return nil, fmt.Errorf("failed to get tar header: %w", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buf, tr); err != nil {
|
||||
return nil, fmt.Errorf("failed to read file content from tar: %w", err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
[Unit]
|
||||
Description=Bzzz P2P Task Coordination Agent
|
||||
Documentation=https://github.com/deepblackcloud/bzzz
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=tony
|
||||
Group=tony
|
||||
WorkingDirectory=/home/tony/AI/projects/Bzzz
|
||||
ExecStart=/home/tony/AI/projects/Bzzz/bzzz
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# Environment variables
|
||||
Environment=BZZZ_LOG_LEVEL=info
|
||||
Environment=BZZZ_HIVE_API_URL=https://hive.home.deepblack.cloud
|
||||
Environment=BZZZ_GITHUB_TOKEN_FILE=/home/tony/AI/secrets/passwords_and_tokens/gh-token
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=false
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/home/tony/AI/projects/Bzzz /tmp /home/tony/.config/bzzz
|
||||
|
||||
# Resource limits
|
||||
LimitNOFILE=65536
|
||||
LimitNPROC=4096
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=bzzz-agent
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/deepblackcloud/bzzz/pkg/coordination"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/pkg/coordination"
|
||||
)
|
||||
|
||||
// AntennaeTestSuite runs comprehensive tests for the antennae coordination system
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/deepblackcloud/bzzz/pubsub"
|
||||
"github.com/anthonyrawlins/bzzz/pubsub"
|
||||
)
|
||||
|
||||
// TaskSimulator generates realistic task scenarios for testing antennae coordination
|
||||
@@ -287,7 +287,7 @@ func generateMockRepositories() []MockRepository {
|
||||
{
|
||||
Owner: "deepblackcloud",
|
||||
Name: "bzzz",
|
||||
URL: "https://github.com/deepblackcloud/bzzz",
|
||||
URL: "https://github.com/anthonyrawlins/bzzz",
|
||||
Dependencies: []string{"hive"},
|
||||
Tasks: []MockTask{
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user