Integrate Bzzz P2P task coordination and enhance project management

🔗 Bzzz Integration:
- Added comprehensive Bzzz integration documentation and todos
- Implemented N8N chat workflow architecture for task coordination
- Enhanced project management with Bzzz-specific features
- Added GitHub service for seamless issue synchronization
- Created BzzzIntegration component for frontend management

🎯 Project Management Enhancements:
- Improved project listing and filtering capabilities
- Enhanced authentication and authorization flows
- Added unified coordinator for better task orchestration
- Streamlined project activation and configuration
- Updated API endpoints for Bzzz compatibility

📊 Technical Improvements:
- Updated Docker Swarm configuration for local registry
- Enhanced frontend build with updated assets
- Improved WebSocket connections for real-time updates
- Added comprehensive error handling and logging
- Updated environment configurations for production

 System Integration:
- Successfully tested with Bzzz v1.2 task execution workflow
- Validated GitHub issue discovery and claiming functionality
- Confirmed sandbox-based task execution compatibility
- Verified Docker registry integration

This release enables seamless integration between Hive project management and Bzzz P2P task coordination, creating a complete distributed development ecosystem.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-14 20:56:01 +10:00
parent e89f2f4b7b
commit 3f3eec7f5d
38 changed files with 2591 additions and 932 deletions

View File

@@ -0,0 +1,264 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
InformationCircleIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
XMarkIcon,
EyeIcon,
LinkIcon
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
import { BzzzTask, BzzzRepository, Project } from '../../types/project';
interface BzzzIntegrationProps {
project: Project;
}
export default function BzzzIntegration({ project }: BzzzIntegrationProps) {
const queryClient = useQueryClient();
const [showAllTasks, setShowAllTasks] = useState(false);
// Fetch Bzzz tasks for this project
const { data: bzzzTasks = [], isLoading: tasksLoading } = useQuery({
queryKey: ['bzzz-tasks', project.id],
queryFn: async (): Promise<BzzzTask[]> => {
const response = await fetch(`/api/bzzz/projects/${project.id}/tasks`);
if (!response.ok) throw new Error('Failed to fetch Bzzz tasks');
return response.json();
},
enabled: !!project.bzzz_config?.bzzz_enabled
});
// Fetch active repositories to check if this project is discoverable
const { data: activeRepos = [] } = useQuery({
queryKey: ['bzzz-active-repos'],
queryFn: async (): Promise<{ repositories: BzzzRepository[] }> => {
const response = await fetch('/api/bzzz/active-repos');
if (!response.ok) throw new Error('Failed to fetch active repositories');
return response.json();
}
});
// Toggle project activation for Bzzz
const toggleActivationMutation = useMutation({
mutationFn: async (ready: boolean) => {
const response = await fetch(`/api/projects/${project.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
bzzz_config: {
...project.bzzz_config,
ready_to_claim: ready
}
})
});
if (!response.ok) throw new Error('Failed to update project');
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['project', project.id] });
queryClient.invalidateQueries({ queryKey: ['bzzz-active-repos'] });
toast.success('Project Bzzz status updated!');
},
onError: () => {
toast.error('Failed to update project status');
}
});
if (!project.bzzz_config?.bzzz_enabled) {
return (
<div className="bg-gray-50 rounded-lg p-6">
<div className="text-center">
<InformationCircleIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Bzzz Integration Disabled</h3>
<p className="text-gray-500 mb-4">
This project is not configured for Bzzz P2P task coordination.
</p>
<p className="text-sm text-gray-400">
Enable Bzzz integration in project settings to allow distributed AI agents to discover and work on tasks.
</p>
</div>
</div>
);
}
const isDiscoverable = activeRepos.repositories.some(repo => repo.name === project.name);
const readyToClaim = project.bzzz_config?.ready_to_claim || false;
const hasGitConfig = project.bzzz_config?.git_url;
const displayTasks = showAllTasks ? bzzzTasks : bzzzTasks.slice(0, 5);
return (
<div className="space-y-6">
{/* Status Overview */}
<div className="bg-white rounded-lg border p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-medium text-gray-900">🐝 Bzzz Integration Status</h2>
{isDiscoverable ? (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<CheckCircleIcon className="h-4 w-4 mr-1" />
Discoverable
</span>
) : (
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
<ExclamationTriangleIcon className="h-4 w-4 mr-1" />
Not Discoverable
</span>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Git Repository */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<LinkIcon className="h-8 w-8 text-gray-400 mx-auto mb-2" />
<p className="text-sm font-medium text-gray-900">Git Repository</p>
<p className="text-xs text-gray-500 mt-1">
{hasGitConfig ? (
<a
href={project.bzzz_config.git_url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800"
>
{project.bzzz_config.git_owner}/{project.bzzz_config.git_repository}
</a>
) : (
'Not configured'
)}
</p>
</div>
{/* Available Tasks */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-gray-900">{bzzzTasks.length}</div>
<p className="text-sm font-medium text-gray-900">Available Tasks</p>
<p className="text-xs text-gray-500">With bzzz-task label</p>
</div>
{/* Claim Status */}
<div className="text-center p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-center mb-2">
{readyToClaim ? (
<CheckCircleIcon className="h-8 w-8 text-green-500" />
) : (
<XMarkIcon className="h-8 w-8 text-red-500" />
)}
</div>
<p className="text-sm font-medium text-gray-900">Ready to Claim</p>
<button
onClick={() => toggleActivationMutation.mutate(!readyToClaim)}
disabled={toggleActivationMutation.isPending}
className={`text-xs px-2 py-1 rounded mt-1 ${
readyToClaim
? 'bg-red-100 text-red-700 hover:bg-red-200'
: 'bg-green-100 text-green-700 hover:bg-green-200'
}`}
>
{toggleActivationMutation.isPending
? 'Updating...'
: (readyToClaim ? 'Deactivate' : 'Activate')
}
</button>
</div>
</div>
</div>
{/* GitHub Tasks */}
{hasGitConfig && (
<div className="bg-white rounded-lg border">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-900">GitHub Issues (bzzz-task)</h3>
{bzzzTasks.length > 5 && (
<button
onClick={() => setShowAllTasks(!showAllTasks)}
className="text-sm text-blue-600 hover:text-blue-800"
>
<EyeIcon className="h-4 w-4 inline mr-1" />
{showAllTasks ? 'Show Less' : `Show All (${bzzzTasks.length})`}
</button>
)}
</div>
</div>
<div className="divide-y divide-gray-200">
{tasksLoading ? (
<div className="p-6 text-center text-gray-500">Loading tasks...</div>
) : displayTasks.length === 0 ? (
<div className="p-6 text-center">
<p className="text-gray-500">No issues found with 'bzzz-task' label.</p>
<p className="text-sm text-gray-400 mt-1">
Create GitHub issues and add the 'bzzz-task' label for agents to discover them.
</p>
</div>
) : (
displayTasks.map((task) => (
<div key={task.number} className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<h4 className="text-sm font-medium text-gray-900">
#{task.number}: {task.title}
</h4>
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
task.is_claimed
? 'bg-blue-100 text-blue-800'
: 'bg-green-100 text-green-800'
}`}>
{task.is_claimed ? 'Claimed' : 'Available'}
</span>
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 text-gray-600">
{task.task_type}
</span>
</div>
<p className="text-sm text-gray-600 mb-2 line-clamp-2">
{task.description || 'No description provided.'}
</p>
<div className="flex items-center space-x-4 text-xs text-gray-500">
<span>State: {task.state}</span>
{task.assignees.length > 0 && (
<span>Assigned to: {task.assignees.join(', ')}</span>
)}
<span>Labels: {task.labels.join(', ')}</span>
</div>
</div>
<a
href={task.html_url}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-blue-600 hover:text-blue-800 ml-4"
>
View on GitHub
</a>
</div>
</div>
))
)}
</div>
</div>
)}
{/* Integration Help */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex">
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">How to Use Bzzz Integration</h3>
<div className="mt-2 text-sm text-yellow-700">
<ol className="list-decimal list-inside space-y-1">
<li>Ensure your GitHub repository has issues labeled with 'bzzz-task'</li>
<li>Activate the project using the "Ready to Claim" toggle above</li>
<li>Bzzz agents will discover and coordinate to work on available tasks</li>
<li>Monitor progress through GitHub issue updates and agent coordination</li>
</ol>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -20,6 +20,16 @@ const projectSchema = z.object({
owner: z.string().optional(),
department: z.string().optional(),
priority: z.enum(['low', 'medium', 'high']).optional()
}).optional(),
bzzz_config: z.object({
git_url: z.string().url('Must be a valid Git URL').optional().or(z.literal('')),
git_owner: z.string().optional(),
git_repository: z.string().optional(),
git_branch: z.string().optional(),
bzzz_enabled: z.boolean().optional(),
ready_to_claim: z.boolean().optional(),
private_repo: z.boolean().optional(),
github_token_required: z.boolean().optional()
}).optional()
});
@@ -52,11 +62,46 @@ export default function ProjectForm({ mode, initialData, projectId }: ProjectFor
owner: initialData?.metadata?.owner || '',
department: initialData?.metadata?.department || '',
priority: initialData?.metadata?.priority || 'medium'
},
bzzz_config: {
git_url: initialData?.bzzz_config?.git_url || '',
git_owner: initialData?.bzzz_config?.git_owner || '',
git_repository: initialData?.bzzz_config?.git_repository || '',
git_branch: initialData?.bzzz_config?.git_branch || 'main',
bzzz_enabled: initialData?.bzzz_config?.bzzz_enabled || false,
ready_to_claim: initialData?.bzzz_config?.ready_to_claim || false,
private_repo: initialData?.bzzz_config?.private_repo || false,
github_token_required: initialData?.bzzz_config?.github_token_required || false
}
}
});
const currentTags = watch('tags') || [];
const gitUrl = watch('bzzz_config.git_url') || '';
const bzzzEnabled = watch('bzzz_config.bzzz_enabled') || false;
// Auto-parse Git URL to extract owner and repository
const parseGitUrl = (url: string) => {
if (!url) return;
try {
// Handle GitHub URLs like https://github.com/owner/repo or git@github.com:owner/repo.git
const githubMatch = url.match(/github\.com[/:]([\w-]+)\/([\w-]+)(?:\.git)?$/);
if (githubMatch) {
const [, owner, repo] = githubMatch;
setValue('bzzz_config.git_owner', owner);
setValue('bzzz_config.git_repository', repo);
}
} catch (error) {
console.log('Could not parse Git URL:', error);
}
};
// Watch for Git URL changes and auto-parse
const handleGitUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const url = e.target.value;
parseGitUrl(url);
};
const createProjectMutation = useMutation({
mutationFn: async (data: ProjectFormData) => {
@@ -314,6 +359,181 @@ export default function ProjectForm({ mode, initialData, projectId }: ProjectFor
</div>
</div>
{/* Bzzz Integration Configuration */}
<div className="bg-white shadow-sm rounded-lg">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center space-x-2">
<h2 className="text-lg font-medium text-gray-900">🐝 Bzzz P2P Integration</h2>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
Beta
</span>
</div>
<p className="text-sm text-gray-500 mt-1">
Configure this project for distributed AI task coordination via the Bzzz P2P network.
</p>
</div>
<div className="px-6 py-4 space-y-6">
{/* Enable Bzzz Integration */}
<div>
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="bzzz_enabled"
{...register('bzzz_config.bzzz_enabled')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="bzzz_enabled" className="text-sm font-medium text-gray-700">
Enable Bzzz P2P coordination for this project
</label>
</div>
<p className="text-sm text-gray-500 mt-1 ml-7">
Allow Bzzz agents to discover and work on tasks from this project's GitHub repository.
</p>
</div>
{/* Git Repository Configuration - Only show if Bzzz is enabled */}
{bzzzEnabled && (
<>
{/* Git Repository URL */}
<div>
<label htmlFor="git_url" className="block text-sm font-medium text-gray-700 mb-2">
Git Repository URL *
</label>
<input
type="url"
id="git_url"
{...register('bzzz_config.git_url')}
onChange={handleGitUrlChange}
className="block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="https://github.com/owner/repository"
/>
<p className="mt-1 text-sm text-gray-500">
GitHub repository URL where Bzzz will look for issues labeled with 'bzzz-task'.
</p>
{errors.bzzz_config?.git_url && (
<p className="mt-1 text-sm text-red-600">{errors.bzzz_config.git_url.message}</p>
)}
</div>
{/* Auto-parsed Git Info */}
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="git_owner" className="block text-sm font-medium text-gray-700 mb-2">
Repository Owner
</label>
<input
type="text"
id="git_owner"
{...register('bzzz_config.git_owner')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Auto-detected from URL"
readOnly
/>
</div>
<div>
<label htmlFor="git_repository" className="block text-sm font-medium text-gray-700 mb-2">
Repository Name
</label>
<input
type="text"
id="git_repository"
{...register('bzzz_config.git_repository')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Auto-detected from URL"
readOnly
/>
</div>
</div>
{/* Git Branch */}
<div>
<label htmlFor="git_branch" className="block text-sm font-medium text-gray-700 mb-2">
Default Branch
</label>
<input
type="text"
id="git_branch"
{...register('bzzz_config.git_branch')}
className="block w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="main"
/>
</div>
{/* Repository Configuration */}
<div className="space-y-3">
<h3 className="text-sm font-medium text-gray-700">Repository Configuration</h3>
<div className="space-y-2">
{/* Ready to Claim */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="ready_to_claim"
{...register('bzzz_config.ready_to_claim')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="ready_to_claim" className="text-sm text-gray-700">
Ready for task claims (agents can start working immediately)
</label>
</div>
{/* Private Repository */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="private_repo"
{...register('bzzz_config.private_repo')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="private_repo" className="text-sm text-gray-700">
Private repository (requires authentication)
</label>
</div>
{/* GitHub Token Required */}
<div className="flex items-center space-x-3">
<input
type="checkbox"
id="github_token_required"
{...register('bzzz_config.github_token_required')}
className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label htmlFor="github_token_required" className="text-sm text-gray-700">
Requires GitHub token for API access
</label>
</div>
</div>
</div>
{/* Bzzz Integration Info */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<div className="flex">
<InformationCircleIcon className="h-5 w-5 text-yellow-400" />
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">
How Bzzz Integration Works
</h3>
<div className="mt-2 text-sm text-yellow-700">
<p>When enabled, Bzzz agents will:</p>
<ul className="list-disc list-inside mt-1 space-y-1">
<li>Monitor GitHub issues labeled with 'bzzz-task'</li>
<li>Coordinate P2P to assign tasks based on agent capabilities</li>
<li>Execute tasks using distributed AI reasoning</li>
<li>Report progress and escalate when needed</li>
</ul>
<p className="mt-2 font-medium">
Make sure your repository has issues labeled with 'bzzz-task' for agents to discover.
</p>
</div>
</div>
</div>
</div>
</>
)}
</div>
</div>
{/* Help Text */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex">

View File

@@ -22,6 +22,7 @@ import { projectApi } from '../../services/api';
export default function ProjectList() {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive' | 'archived'>('all');
const [bzzzFilter, setBzzzFilter] = useState<'all' | 'enabled' | 'disabled'>('all');
// Fetch real projects from API
const { data: projects = [], isLoading, error } = useQuery({
@@ -35,7 +36,13 @@ export default function ProjectList() {
const matchesSearch = project.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.description?.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || project.status === statusFilter;
return matchesSearch && matchesStatus;
const bzzzEnabled = (project as any).bzzz_config?.bzzz_enabled || false;
const matchesBzzz = bzzzFilter === 'all' ||
(bzzzFilter === 'enabled' && bzzzEnabled) ||
(bzzzFilter === 'disabled' && !bzzzEnabled);
return matchesSearch && matchesStatus && matchesBzzz;
});
const getStatusBadge = (status: string) => {
@@ -134,6 +141,19 @@ export default function ProjectList() {
<option value="archived">Archived</option>
</select>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-500">🐝</span>
<select
value={bzzzFilter}
onChange={(e) => setBzzzFilter(e.target.value as any)}
className="border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">All Projects</option>
<option value="enabled">Bzzz Enabled</option>
<option value="disabled">Bzzz Disabled</option>
</select>
</div>
</div>
</div>
@@ -213,6 +233,16 @@ export default function ProjectList() {
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<Link
to={`/projects/${project.id}/bzzz`}
className={`${active ? 'bg-gray-100' : ''} block px-4 py-2 text-sm text-gray-700`}
>
🐝 Bzzz Integration
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
@@ -233,9 +263,20 @@ export default function ProjectList() {
{/* Status and Tags */}
<div className="flex items-center justify-between mt-4">
<span className={getStatusBadge(project.status)}>
{project.status}
</span>
<div className="flex items-center space-x-2">
<span className={getStatusBadge(project.status)}>
{project.status}
</span>
{/* Bzzz Integration Status */}
{(project as any).bzzz_config?.bzzz_enabled && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
🐝 Bzzz
{(project as any).bzzz_config?.ready_to_claim && (
<span className="ml-1 inline-block w-2 h-2 bg-green-400 rounded-full"></span>
)}
</span>
)}
</div>
<div className="flex items-center space-x-1">
{project.tags?.slice(0, 2).map((tag) => (
<span key={tag} className="inline-flex items-center px-2 py-1 rounded text-xs bg-gray-100 text-gray-600">
@@ -248,6 +289,21 @@ export default function ProjectList() {
)}
</div>
</div>
{/* GitHub Repository Info for Bzzz-enabled projects */}
{(project as any).bzzz_config?.bzzz_enabled && (project as any).bzzz_config?.git_url && (
<div className="mt-3 text-xs text-gray-500">
<div className="flex items-center space-x-1">
<svg className="h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" />
</svg>
<span>{(project as any).bzzz_config.git_owner}/{(project as any).bzzz_config.git_repository}</span>
{(project as any).bzzz_config.ready_to_claim && (
<span className="text-green-600"> Ready for tasks</span>
)}
</div>
</div>
)}
</div>
{/* Metrics */}