MAJOR BREAKTHROUGH - BZZZ now compiles past structural issues! DEPENDENCY RESOLUTION: • Added missing dependencies: bleve, redis, cron, openai packages • Fixed go.mod/go.sum conflicts with updated crypto packages • Resolved all golang.org/x package version conflicts TYPE SYSTEM FIXES: • Fixed corrupted pkg/agentid/crypto.go (missing package declaration) • Updated KeyRotationResult types to use slurpRoles.KeyRotationResult • Fixed AccessControlMatrix field mismatches (roleHierarchy as map vs struct) • Corrected RoleEncryptionConfig field access (EncryptionKeys not Keys) • Updated RoleKey types to use proper qualified names CODE ORGANIZATION: • Moved test/chat_api_handler.go → cmd/chat-api/main.go (resolved package conflicts) • Cleaned up unused imports across crypto package files • Commented out problematic audit logger sections (temporary) • Fixed brace mismatch in GetSecurityMetrics function BUILD STATUS IMPROVEMENT: • BEFORE: Import cycle errors preventing any compilation • AFTER: Clean compilation through crypto package, now hitting DHT API issues • This represents moving from structural blockers to routine API compatibility fixes SIGNIFICANCE: This commit represents the successful resolution of all major architectural blocking issues. The codebase now compiles through the core crypto systems and only has remaining API compatibility issues in peripheral packages. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
423 lines
12 KiB
Go
423 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"chorus.services/bzzz/executor"
|
|
"chorus.services/bzzz/logging"
|
|
"chorus.services/bzzz/pkg/types"
|
|
"chorus.services/bzzz/sandbox"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// ChatTaskRequest represents a task request from the chat interface
|
|
type ChatTaskRequest struct {
|
|
Method string `json:"method"`
|
|
Task *types.EnhancedTask `json:"task"`
|
|
ExecutionOptions *ExecutionOptions `json:"execution_options"`
|
|
Callback *CallbackConfig `json:"callback"`
|
|
}
|
|
|
|
// ExecutionOptions defines how the task should be executed
|
|
type ExecutionOptions struct {
|
|
SandboxImage string `json:"sandbox_image"`
|
|
Timeout string `json:"timeout"`
|
|
MaxIterations int `json:"max_iterations"`
|
|
ReturnFullLog bool `json:"return_full_log"`
|
|
CleanupOnComplete bool `json:"cleanup_on_complete"`
|
|
}
|
|
|
|
// CallbackConfig defines where to send results
|
|
type CallbackConfig struct {
|
|
WebhookURL string `json:"webhook_url"`
|
|
IncludeArtifacts bool `json:"include_artifacts"`
|
|
}
|
|
|
|
// ChatTaskResponse represents the response from task execution
|
|
type ChatTaskResponse struct {
|
|
TaskID int `json:"task_id"`
|
|
Status string `json:"status"`
|
|
ExecutionTime string `json:"execution_time"`
|
|
Artifacts *ExecutionArtifacts `json:"artifacts,omitempty"`
|
|
ExecutionLog []ExecutionLogEntry `json:"execution_log,omitempty"`
|
|
Errors []ExecutionError `json:"errors,omitempty"`
|
|
GitBranch string `json:"git_branch,omitempty"`
|
|
PullRequestURL string `json:"pr_url,omitempty"`
|
|
OriginalRequest *ChatTaskRequest `json:"original_request,omitempty"`
|
|
}
|
|
|
|
// ExecutionArtifacts contains the outputs of task execution
|
|
type ExecutionArtifacts struct {
|
|
FilesCreated []FileArtifact `json:"files_created,omitempty"`
|
|
CodeGenerated string `json:"code_generated,omitempty"`
|
|
Language string `json:"language,omitempty"`
|
|
TestsCreated []FileArtifact `json:"tests_created,omitempty"`
|
|
Documentation string `json:"documentation,omitempty"`
|
|
}
|
|
|
|
// FileArtifact represents a file created during execution
|
|
type FileArtifact struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Size int64 `json:"size"`
|
|
Content string `json:"content,omitempty"`
|
|
Language string `json:"language,omitempty"`
|
|
}
|
|
|
|
// ExecutionLogEntry represents a single step in the execution process
|
|
type ExecutionLogEntry struct {
|
|
Step int `json:"step"`
|
|
Action string `json:"action"`
|
|
Command string `json:"command,omitempty"`
|
|
Result string `json:"result"`
|
|
Success bool `json:"success"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Duration string `json:"duration,omitempty"`
|
|
}
|
|
|
|
// ExecutionError represents an error that occurred during execution
|
|
type ExecutionError struct {
|
|
Step int `json:"step,omitempty"`
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
Command string `json:"command,omitempty"`
|
|
}
|
|
|
|
// ChatAPIHandler handles chat integration requests
|
|
type ChatAPIHandler struct {
|
|
logger *logging.HypercoreLog
|
|
}
|
|
|
|
// NewChatAPIHandler creates a new chat API handler
|
|
func NewChatAPIHandler() *ChatAPIHandler {
|
|
// Note: HypercoreLog expects a peer.ID, but for testing we use nil
|
|
// In production, this should be integrated with the actual P2P peer ID
|
|
|
|
return &ChatAPIHandler{
|
|
logger: nil, // Will be set up when P2P integration is available
|
|
}
|
|
}
|
|
|
|
// ExecuteTaskHandler handles task execution requests from N8N chat workflow
|
|
func (h *ChatAPIHandler) ExecuteTaskHandler(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
// Parse request
|
|
var req ChatTaskRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
h.sendError(w, http.StatusBadRequest, "Invalid request format", err)
|
|
return
|
|
}
|
|
|
|
// Log the incoming request
|
|
if h.logger != nil {
|
|
h.logger.Append(logging.TaskProgress, map[string]interface{}{
|
|
"task_id": req.Task.Number,
|
|
"method": req.Method,
|
|
"source": "chat_api",
|
|
"status": "received",
|
|
})
|
|
}
|
|
|
|
// Validate request
|
|
if req.Task == nil {
|
|
h.sendError(w, http.StatusBadRequest, "Task is required", nil)
|
|
return
|
|
}
|
|
|
|
// Send immediate response to N8N
|
|
response := map[string]interface{}{
|
|
"task_id": req.Task.Number,
|
|
"status": "accepted",
|
|
"message": "Task accepted for execution",
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
|
|
// Execute task asynchronously
|
|
go h.executeTaskAsync(ctx, &req)
|
|
}
|
|
|
|
// executeTaskAsync executes the task in a separate goroutine
|
|
func (h *ChatAPIHandler) executeTaskAsync(ctx context.Context, req *ChatTaskRequest) {
|
|
startTime := time.Now()
|
|
var response ChatTaskResponse
|
|
|
|
response.TaskID = req.Task.Number
|
|
response.OriginalRequest = req
|
|
|
|
// Create execution log
|
|
var executionLog []ExecutionLogEntry
|
|
var artifacts ExecutionArtifacts
|
|
var errors []ExecutionError
|
|
|
|
defer func() {
|
|
response.ExecutionTime = time.Since(startTime).String()
|
|
response.ExecutionLog = executionLog
|
|
response.Artifacts = &artifacts
|
|
response.Errors = errors
|
|
|
|
// Send callback to N8N
|
|
if req.Callback != nil && req.Callback.WebhookURL != "" {
|
|
h.sendCallback(req.Callback.WebhookURL, &response)
|
|
}
|
|
}()
|
|
|
|
// Log start of execution
|
|
executionLog = append(executionLog, ExecutionLogEntry{
|
|
Step: 1,
|
|
Action: "Starting task execution",
|
|
Result: fmt.Sprintf("Task: %s", req.Task.Title),
|
|
Success: true,
|
|
Timestamp: time.Now(),
|
|
})
|
|
|
|
// Create sandbox
|
|
sb, err := sandbox.CreateSandbox(ctx, req.ExecutionOptions.SandboxImage)
|
|
if err != nil {
|
|
response.Status = "failed"
|
|
errors = append(errors, ExecutionError{
|
|
Step: 2,
|
|
Type: "sandbox_creation_failed",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Ensure cleanup
|
|
defer func() {
|
|
if req.ExecutionOptions.CleanupOnComplete {
|
|
sb.DestroySandbox()
|
|
}
|
|
}()
|
|
|
|
executionLog = append(executionLog, ExecutionLogEntry{
|
|
Step: 2,
|
|
Action: "Created sandbox",
|
|
Result: fmt.Sprintf("Sandbox ID: %s", sb.ID[:12]),
|
|
Success: true,
|
|
Timestamp: time.Now(),
|
|
})
|
|
|
|
// Clone repository if specified
|
|
if req.Task.GitURL != "" {
|
|
cloneCmd := fmt.Sprintf("git clone %s .", req.Task.GitURL)
|
|
result, err := sb.RunCommand(cloneCmd)
|
|
|
|
success := err == nil
|
|
executionLog = append(executionLog, ExecutionLogEntry{
|
|
Step: 3,
|
|
Action: "Clone repository",
|
|
Command: cloneCmd,
|
|
Result: fmt.Sprintf("Exit: %d, Output: %s", result.ExitCode, result.StdOut),
|
|
Success: success,
|
|
Timestamp: time.Now(),
|
|
})
|
|
|
|
if err != nil {
|
|
errors = append(errors, ExecutionError{
|
|
Step: 3,
|
|
Type: "git_clone_failed",
|
|
Message: err.Error(),
|
|
Command: cloneCmd,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Execute the task using the existing executor
|
|
result, err := executor.ExecuteTask(ctx, req.Task, h.logger)
|
|
if err != nil {
|
|
response.Status = "failed"
|
|
errors = append(errors, ExecutionError{
|
|
Type: "execution_failed",
|
|
Message: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Collect artifacts from sandbox
|
|
h.collectArtifacts(sb, &artifacts)
|
|
|
|
// Set success status
|
|
response.Status = "success"
|
|
if result.BranchName != "" {
|
|
response.GitBranch = result.BranchName
|
|
}
|
|
|
|
executionLog = append(executionLog, ExecutionLogEntry{
|
|
Step: len(executionLog) + 1,
|
|
Action: "Task completed successfully",
|
|
Result: fmt.Sprintf("Files created: %d", len(artifacts.FilesCreated)),
|
|
Success: true,
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
|
|
// collectArtifacts gathers files and outputs from the sandbox
|
|
func (h *ChatAPIHandler) collectArtifacts(sb *sandbox.Sandbox, artifacts *ExecutionArtifacts) {
|
|
// List files created in workspace
|
|
result, err := sb.RunCommand("find . -type f -name '*.py' -o -name '*.js' -o -name '*.go' -o -name '*.java' -o -name '*.cpp' -o -name '*.rs' | head -20")
|
|
if err == nil && result.StdOut != "" {
|
|
files := strings.Split(strings.TrimSpace(result.StdOut), "\n")
|
|
var validFiles []string
|
|
for _, line := range files {
|
|
if strings.TrimSpace(line) != "" {
|
|
validFiles = append(validFiles, strings.TrimSpace(line))
|
|
}
|
|
}
|
|
files = validFiles
|
|
|
|
for _, file := range files {
|
|
// Get file content
|
|
content, err := sb.ReadFile(file)
|
|
if err == nil && len(content) < 10000 { // Limit content size
|
|
stat, _ := sb.RunCommand(fmt.Sprintf("stat -c '%%s' %s", file))
|
|
size := int64(0)
|
|
if stat.ExitCode == 0 {
|
|
fmt.Sscanf(stat.StdOut, "%d", &size)
|
|
}
|
|
|
|
artifact := FileArtifact{
|
|
Name: file,
|
|
Path: file,
|
|
Size: size,
|
|
Content: string(content),
|
|
Language: h.detectLanguage(file),
|
|
}
|
|
artifacts.FilesCreated = append(artifacts.FilesCreated, artifact)
|
|
|
|
// If this looks like the main generated code, set it
|
|
if artifacts.CodeGenerated == "" && size > 0 {
|
|
artifacts.CodeGenerated = string(content)
|
|
artifacts.Language = artifact.Language
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// detectLanguage detects programming language from file extension
|
|
func (h *ChatAPIHandler) detectLanguage(filename string) string {
|
|
extensions := map[string]string{
|
|
".py": "python",
|
|
".js": "javascript",
|
|
".ts": "typescript",
|
|
".go": "go",
|
|
".java": "java",
|
|
".cpp": "cpp",
|
|
".c": "c",
|
|
".rs": "rust",
|
|
".rb": "ruby",
|
|
".php": "php",
|
|
}
|
|
|
|
for ext, lang := range extensions {
|
|
if len(filename) > len(ext) && filename[len(filename)-len(ext):] == ext {
|
|
return lang
|
|
}
|
|
}
|
|
return "text"
|
|
}
|
|
|
|
// sendCallback sends the execution results back to N8N webhook
|
|
func (h *ChatAPIHandler) sendCallback(webhookURL string, response *ChatTaskResponse) {
|
|
jsonData, err := json.Marshal(response)
|
|
if err != nil {
|
|
log.Printf("Failed to marshal callback response: %v", err)
|
|
return
|
|
}
|
|
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
log.Printf("Failed to send callback to %s: %v", webhookURL, err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
log.Printf("Callback webhook returned status %d", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
// sendError sends an error response
|
|
func (h *ChatAPIHandler) sendError(w http.ResponseWriter, statusCode int, message string, err error) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
|
|
errorResponse := map[string]interface{}{
|
|
"error": message,
|
|
"status": statusCode,
|
|
}
|
|
|
|
if err != nil {
|
|
errorResponse["details"] = err.Error()
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(errorResponse)
|
|
}
|
|
|
|
// HealthHandler provides a health check endpoint
|
|
func (h *ChatAPIHandler) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"status": "healthy",
|
|
"service": "bzzz-chat-api",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
// StartChatAPIServer starts the HTTP server for chat integration
|
|
func StartChatAPIServer(port string) {
|
|
handler := NewChatAPIHandler()
|
|
|
|
r := mux.NewRouter()
|
|
|
|
// API routes
|
|
api := r.PathPrefix("/bzzz/api").Subrouter()
|
|
api.HandleFunc("/execute-task", handler.ExecuteTaskHandler).Methods("POST")
|
|
api.HandleFunc("/health", handler.HealthHandler).Methods("GET")
|
|
|
|
// Add CORS middleware
|
|
r.Use(func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
})
|
|
|
|
log.Printf("🚀 Starting Bzzz Chat API server on port %s", port)
|
|
log.Printf("📡 Endpoints:")
|
|
log.Printf(" POST /bzzz/api/execute-task - Execute task in sandbox")
|
|
log.Printf(" GET /bzzz/api/health - Health check")
|
|
|
|
if err := http.ListenAndServe(":"+port, r); err != nil {
|
|
log.Fatalf("Failed to start server: %v", err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
port := "8080"
|
|
if len(os.Args) > 1 {
|
|
port = os.Args[1]
|
|
}
|
|
|
|
StartChatAPIServer(port)
|
|
} |