diff --git a/app/Console/Commands/AgentRunCommand.php b/app/Console/Commands/AgentRunCommand.php new file mode 100644 index 0000000..f83c6c5 --- /dev/null +++ b/app/Console/Commands/AgentRunCommand.php @@ -0,0 +1,106 @@ +promptsDir = resource_path('prompts'); + } + + public function handle(): int + { + $swarmId = $this->option('swarm'); + $agentId = $this->option('agent'); + $role = $this->option('role'); + + if (!$swarmId || !$agentId || !$role) { + $this->error('Missing required options: --swarm, --agent, --role'); + return self::FAILURE; + } + + $status = $this->statusService->get($agentId); + + if (!$status) { + $this->error("Agent status not found: {$agentId}"); + return self::FAILURE; + } + + // Load the prompt for this role + $promptFile = "{$this->promptsDir}/{$role}.md"; + if (!File::exists($promptFile)) { + $this->statusService->fail($agentId, "Prompt file not found: {$role}.md"); + return self::FAILURE; + } + + $prompt = File::get($promptFile); + + try { + // Update status to running + $this->statusService->setStatus($agentId, 'running'); + $this->statusService->setProgress($agentId, 0); + + // Simulate work with progress updates + // In production, this would connect to Claude API + $this->executeWork($agentId, $prompt, $role); + + // Mark as completed + $this->statusService->complete($agentId, $this->generateOutput($role)); + + return self::SUCCESS; + } catch (\Exception $e) { + $this->statusService->fail($agentId, $e->getMessage()); + + return self::FAILURE; + } + } + + protected function executeWork(string $agentId, string $prompt, string $role): void + { + // Simulate work phases with progress updates + $phases = [ + ['progress' => 25, 'phase' => 'Analyzing task', 'delay' => 2], + ['progress' => 50, 'phase' => 'Processing context', 'delay' => 3], + ['progress' => 75, 'phase' => 'Generating solution', 'delay' => 3], + ['progress' => 90, 'phase' => 'Finalizing output', 'delay' => 2], + ]; + + foreach ($phases as $phase) { + sleep($phase['delay']); + + $this->statusService->update($agentId, [ + 'progress' => $phase['progress'], + 'current_phase' => $phase['phase'], + ]); + } + } + + protected function generateOutput(string $role): string + { + // Simulate role-specific output + // In production, this would be the actual Claude response + return match ($role) { + 'architect' => "Architecture analysis complete:\n- Reviewed codebase structure\n- Identified key components\n- Proposed implementation plan", + 'implementer' => "Implementation complete:\n- Added new features\n- Updated tests\n- Verified functionality", + 'tester' => "Testing complete:\n- All tests passing\n- Code coverage: 95%\n- No critical issues found", + 'reviewer' => "Code review complete:\n- Code quality: Good\n- Security: No issues\n- Performance: Optimized", + default => "Task completed successfully for role: {$role}", + }; + } +} diff --git a/app/Console/Commands/AgentSwarmCommand.php b/app/Console/Commands/AgentSwarmCommand.php new file mode 100644 index 0000000..f9ac626 --- /dev/null +++ b/app/Console/Commands/AgentSwarmCommand.php @@ -0,0 +1,157 @@ +option('kit') ?? select( + label: 'Which agent kit do you want to deploy?', + options: [ + 'github-solver' => 'GitHub Issue Solver (architect + implementer + tester + reviewer)', + 'quick-fix' => 'Quick Fix (single implementer)', + 'code-review' => 'Code Review (security + performance + style reviewers)', + 'custom' => 'Custom swarm configuration', + ], + default: 'github-solver', + ); + + $agents = $this->getAgentsForKit($kit); + + if ($this->option('dry-run')) { + $this->showDryRun($kit, $agents); + return self::SUCCESS; + } + + // Generate swarm ID + $swarmId = $this->generateSnowflakeId(); + + info("Deploying swarm: {$swarmId}"); + info("Kit: {$kit}"); + info("Agents: " . count($agents)); + + // Show what we're deploying + table( + ['Agent', 'Role', 'Queue', 'Status'], + collect($agents)->map(fn ($a) => [$a['name'], $a['role'], $a['queue'], 'Pending'])->toArray() + ); + + if (!confirm('Deploy this swarm?', true)) { + error('Aborted.'); + return self::FAILURE; + } + + // Deploy each agent + foreach ($agents as $agent) { + $agentId = $this->generateSnowflakeId(); + + $this->deployAgent($swarmId, $agentId, $agent); + + info("✓ {$agent['name']} deployed ({$agentId})"); + } + + info(''); + info("Swarm deployed! Monitor with:"); + info(" php artisan agent:watch --swarm={$swarmId}"); + + return self::SUCCESS; + } + + protected function getAgentsForKit(string $kit): array + { + return match ($kit) { + 'github-solver' => [ + ['name' => 'Architect', 'role' => 'architect', 'queue' => 'agent:architect', 'prompt' => 'architect.md'], + ['name' => 'Implementer', 'role' => 'implementer', 'queue' => 'agent:implementer', 'prompt' => 'implementer.md'], + ['name' => 'Tester', 'role' => 'tester', 'queue' => 'agent:tester', 'prompt' => 'tester.md'], + ['name' => 'Reviewer', 'role' => 'reviewer', 'queue' => 'agent:reviewer', 'prompt' => 'reviewer.md'], + ], + 'quick-fix' => [ + ['name' => 'Implementer', 'role' => 'implementer', 'queue' => 'agent:implementer', 'prompt' => 'implementer.md'], + ], + 'code-review' => [ + ['name' => 'Security Reviewer', 'role' => 'security', 'queue' => 'agent:reviewer', 'prompt' => 'security-reviewer.md'], + ['name' => 'Performance Reviewer', 'role' => 'performance', 'queue' => 'agent:reviewer', 'prompt' => 'performance-reviewer.md'], + ['name' => 'Style Reviewer', 'role' => 'style', 'queue' => 'agent:reviewer', 'prompt' => 'style-reviewer.md'], + ], + default => [], + }; + } + + protected function deployAgent(string $swarmId, string $agentId, array $agent): void + { + // Create initial status using the service + $this->statusService->create($agentId, $swarmId, $agent); + + // Update to 'starting' status + $this->statusService->setStatus($agentId, 'starting'); + + // Option 1: Dispatch as queue job + // AgentTask::dispatch($swarmId, $agentId, $agent)->onQueue($agent['queue']); + + // Option 2: Run as background process (for Claude Code agents) + $this->runBackgroundAgent($swarmId, $agentId, $agent); + } + + protected function runBackgroundAgent(string $swarmId, string $agentId, array $agent): void + { + // This could exec claude-code CLI or dispatch to Docker container + // For now, we'll use a wrapper script + + $command = sprintf( + 'php artisan agent:run --swarm=%s --agent=%s --role=%s > /dev/null 2>&1 &', + $swarmId, + $agentId, + $agent['role'] + ); + + exec($command); + } + + protected function generateSnowflakeId(): string + { + // Simple snowflake-like ID (timestamp + random) + // In production, use godruoyi/snowflake + return (string) (intval(microtime(true) * 1000) . random_int(1000, 9999)); + } + + protected function showDryRun(string $kit, array $agents): void + { + info("[DRY RUN] Would deploy:"); + info("Kit: {$kit}"); + + table( + ['Agent', 'Role', 'Queue', 'Prompt'], + collect($agents)->map(fn ($a) => [$a['name'], $a['role'], $a['queue'], $a['prompt']])->toArray() + ); + } +} diff --git a/app/Console/Commands/AgentWatchCommand.php b/app/Console/Commands/AgentWatchCommand.php new file mode 100644 index 0000000..f0973c8 --- /dev/null +++ b/app/Console/Commands/AgentWatchCommand.php @@ -0,0 +1,188 @@ +option('swarm'); + $interval = (int) $this->option('interval'); + $once = $this->option('once'); + + $this->info('Agent Swarm Monitor'); + $this->info('Press Ctrl+C to exit'); + $this->newLine(); + + do { + $this->clearScreen(); + $this->renderDashboard($swarmFilter); + + if (!$once) { + sleep($interval); + } + } while (!$once && $this->shouldContinue()); + + return self::SUCCESS; + } + + protected function renderDashboard(?string $swarmFilter): void + { + $agents = $this->loadAgentStatuses($swarmFilter); + + if ($agents->isEmpty()) { + $this->warn('No agents found.'); + return; + } + + // Group by swarm + $swarms = $agents->groupBy('swarm_id'); + + foreach ($swarms as $swarmId => $swarmAgents) { + $this->renderSwarmHeader($swarmId, $swarmAgents); + $this->renderAgentTable($swarmAgents); + $this->newLine(); + } + + $this->renderSummary($agents); + } + + protected function renderSwarmHeader(string $swarmId, Collection $agents): void + { + $completed = $agents->where('status', 'completed')->count(); + $total = $agents->count(); + $percent = $total > 0 ? round(($completed / $total) * 100) : 0; + + $this->info("╔══════════════════════════════════════════════════════════════╗"); + $this->info("║ SWARM: {$swarmId}"); + $this->info("║ Progress: {$completed}/{$total} agents ({$percent}%)"); + $this->info("╚══════════════════════════════════════════════════════════════╝"); + } + + protected function renderAgentTable(Collection $agents): void + { + $rows = $agents->map(function ($agent) { + $progress = $this->renderProgressBar($agent['progress'] ?? 0); + $status = $this->colorizeStatus($agent['status']); + $duration = $this->calculateDuration($agent); + + return [ + $agent['name'] ?? 'Unknown', + $agent['role'] ?? '-', + $progress, + $status, + $duration, + ]; + })->toArray(); + + $this->table( + ['Agent', 'Role', 'Progress', 'Status', 'Duration'], + $rows + ); + } + + protected function renderProgressBar(int $percent, int $width = 20): string + { + $filled = (int) round(($percent / 100) * $width); + $empty = $width - $filled; + + $bar = str_repeat('█', $filled) . str_repeat('░', $empty); + + return "{$bar} {$percent}%"; + } + + protected function colorizeStatus(string $status): string + { + return match ($status) { + 'completed' => "✓ {$status}", + 'running', 'in_progress' => "● {$status}", + 'failed', 'error' => "✗ {$status}", + 'starting', 'pending' => "○ {$status}", + default => $status, + }; + } + + protected function calculateDuration(array $agent): string + { + $start = $agent['started_at'] ?? null; + if (!$start) { + return '-'; + } + + $end = $agent['completed_at'] ?? now()->toIso8601String(); + + $startTime = strtotime($start); + $endTime = strtotime($end); + $seconds = $endTime - $startTime; + + if ($seconds < 60) { + return "{$seconds}s"; + } + + $minutes = floor($seconds / 60); + $secs = $seconds % 60; + + return "{$minutes}m {$secs}s"; + } + + protected function renderSummary(Collection $agents): void + { + $total = $agents->count(); + $completed = $agents->where('status', 'completed')->count(); + $running = $agents->whereIn('status', ['running', 'in_progress', 'starting'])->count(); + $failed = $agents->whereIn('status', ['failed', 'error'])->count(); + $pending = $agents->where('status', 'pending')->count(); + + $this->line("─────────────────────────────────────────────────────────────────"); + $this->line(" Total: {$total} | ✓ Completed: {$completed} | ● Running: {$running} | ✗ Failed: {$failed} | ○ Pending: {$pending}"); + $this->line(" Last updated: " . now()->format('H:i:s')); + } + + protected function loadAgentStatuses(?string $swarmFilter): Collection + { + if ($swarmFilter) { + return $this->statusService->getBySwarm($swarmFilter) + ->sortBy('created_at'); + } + + return $this->statusService->getAll() + ->sortBy('created_at'); + } + + protected function clearScreen(): void + { + if (PHP_OS_FAMILY === 'Windows') { + system('cls'); + } else { + system('clear'); + } + } + + protected function shouldContinue(): bool + { + // Check if all agents are done + $agents = $this->loadAgentStatuses($this->option('swarm')); + + $allDone = $agents->every(fn ($a) => in_array($a['status'], ['completed', 'failed', 'error'])); + + return !$allDone; + } +} diff --git a/app/Enums/AgentStatus.php b/app/Enums/AgentStatus.php new file mode 100644 index 0000000..08723a4 --- /dev/null +++ b/app/Enums/AgentStatus.php @@ -0,0 +1,14 @@ +where('user_id', $request->user()->id) + ->with(['members.agent']) + ->orderByDesc('created_at') + ->get() + ->map(function ($swarm) { + return [ + 'id' => $swarm->id, + 'name' => $swarm->name, + 'description' => $swarm->description, + 'status' => $swarm->status, + 'total_agents' => $swarm->total_agents, + 'completed_agents' => $swarm->completed_agents, + 'failed_agents' => $swarm->failed_agents, + 'progress_percentage' => $swarm->progress_percentage, + 'started_at' => $swarm->started_at?->toIso8601String(), + 'completed_at' => $swarm->completed_at?->toIso8601String(), + 'members' => $swarm->members->map(function ($member) { + return [ + 'id' => $member->id, + 'name' => $member->name, + 'role' => $member->role, + 'status' => $member->status, + 'progress' => $member->progress, + 'current_task' => $member->current_task, + 'error' => $member->error, + 'started_at' => $member->started_at?->toIso8601String(), + 'completed_at' => $member->completed_at?->toIso8601String(), + ]; + }), + ]; + }); + + return Inertia::render('Agents/Swarm', [ + 'swarms' => $swarms, + ]); + } + + /** + * Get swarm details by ID. + */ + public function show(Request $request, AgentSwarm $swarm): JsonResponse + { + abort_unless($swarm->user_id === $request->user()->id, 403); + + return response()->json([ + 'id' => $swarm->id, + 'name' => $swarm->name, + 'description' => $swarm->description, + 'status' => $swarm->status, + 'total_agents' => $swarm->total_agents, + 'completed_agents' => $swarm->completed_agents, + 'failed_agents' => $swarm->failed_agents, + 'progress_percentage' => $swarm->progress_percentage, + 'started_at' => $swarm->started_at?->toIso8601String(), + 'completed_at' => $swarm->completed_at?->toIso8601String(), + 'members' => $swarm->members->map(function ($member) { + return [ + 'id' => $member->id, + 'name' => $member->name, + 'role' => $member->role, + 'status' => $member->status, + 'progress' => $member->progress, + 'current_task' => $member->current_task, + 'error' => $member->error, + 'started_at' => $member->started_at?->toIso8601String(), + 'completed_at' => $member->completed_at?->toIso8601String(), + ]; + }), + ]); + } + + /** + * Update agent status within a swarm. + */ + public function updateAgentStatus(Request $request): JsonResponse + { + $validated = $request->validate([ + 'swarm_id' => 'required|exists:agent_swarms,id', + 'agent_id' => 'required|exists:agent_swarm_members,id', + 'status' => 'required|in:pending,running,completed,failed', + 'progress' => 'nullable|integer|min:0|max:100', + 'current_task' => 'nullable|string', + 'error' => 'nullable|string', + ]); + + $swarm = AgentSwarm::findOrFail($validated['swarm_id']); + abort_unless($swarm->user_id === $request->user()->id, 403); + + $member = AgentSwarmMember::findOrFail($validated['agent_id']); + + $member->update([ + 'status' => $validated['status'], + 'progress' => $validated['progress'] ?? $member->progress, + 'current_task' => $validated['current_task'] ?? $member->current_task, + 'error' => $validated['error'] ?? $member->error, + 'started_at' => $member->started_at ?? ($validated['status'] === 'running' ? now() : null), + 'completed_at' => in_array($validated['status'], ['completed', 'failed']) ? now() : null, + ]); + + // Update swarm counts + $this->updateSwarmCounts($swarm); + + return response()->json(['success' => true]); + } + + /** + * Create a new swarm. + */ + public function store(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'agents' => 'required|array|min:1', + 'agents.*.agent_id' => 'required|exists:agents,id', + 'agents.*.name' => 'required|string', + 'agents.*.role' => 'nullable|string', + ]); + + $swarm = AgentSwarm::create([ + 'name' => $validated['name'], + 'description' => $validated['description'] ?? null, + 'user_id' => $request->user()->id, + 'status' => 'pending', + 'total_agents' => count($validated['agents']), + ]); + + foreach ($validated['agents'] as $agentData) { + AgentSwarmMember::create([ + 'swarm_id' => $swarm->id, + 'agent_id' => $agentData['agent_id'], + 'name' => $agentData['name'], + 'role' => $agentData['role'] ?? null, + 'status' => 'pending', + ]); + } + + return response()->json(['swarm_id' => $swarm->id], 201); + } + + /** + * Update swarm counts based on member statuses. + */ + private function updateSwarmCounts(AgentSwarm $swarm): void + { + $completedCount = $swarm->members()->where('status', 'completed')->count(); + $failedCount = $swarm->members()->where('status', 'failed')->count(); + + $swarm->update([ + 'completed_agents' => $completedCount, + 'failed_agents' => $failedCount, + 'status' => $this->determineSwarmStatus($swarm, $completedCount, $failedCount), + 'started_at' => $swarm->started_at ?? ($swarm->members()->where('status', 'running')->exists() ? now() : null), + 'completed_at' => ($completedCount + $failedCount === $swarm->total_agents) ? now() : null, + ]); + } + + /** + * Determine swarm status based on member statuses. + */ + private function determineSwarmStatus(AgentSwarm $swarm, int $completedCount, int $failedCount): string + { + if ($completedCount + $failedCount === $swarm->total_agents) { + return $failedCount > 0 ? 'failed' : 'completed'; + } + + if ($swarm->members()->where('status', 'running')->exists()) { + return 'running'; + } + + return 'pending'; + } +} diff --git a/app/Models/AgentSwarm.php b/app/Models/AgentSwarm.php new file mode 100644 index 0000000..92fa25d --- /dev/null +++ b/app/Models/AgentSwarm.php @@ -0,0 +1,63 @@ + 'array', + 'started_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function members(): HasMany + { + return $this->hasMany(AgentSwarmMember::class, 'swarm_id'); + } + + public function getProgressPercentageAttribute(): int + { + if ($this->total_agents === 0) { + return 0; + } + + return (int) (($this->completed_agents / $this->total_agents) * 100); + } + + public function isRunning(): bool + { + return $this->status === 'running'; + } + + public function isCompleted(): bool + { + return $this->status === 'completed'; + } + + public function isFailed(): bool + { + return $this->status === 'failed'; + } +} diff --git a/app/Models/AgentSwarmMember.php b/app/Models/AgentSwarmMember.php new file mode 100644 index 0000000..98baae3 --- /dev/null +++ b/app/Models/AgentSwarmMember.php @@ -0,0 +1,54 @@ + 'array', + 'started_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + public function swarm(): BelongsTo + { + return $this->belongsTo(AgentSwarm::class, 'swarm_id'); + } + + public function agent(): BelongsTo + { + return $this->belongsTo(Agent::class); + } + + public function isRunning(): bool + { + return $this->status === 'running'; + } + + public function isCompleted(): bool + { + return $this->status === 'completed'; + } + + public function isFailed(): bool + { + return $this->status === 'failed'; + } +} diff --git a/app/Services/AgentStatusService.php b/app/Services/AgentStatusService.php new file mode 100644 index 0000000..7e223f8 --- /dev/null +++ b/app/Services/AgentStatusService.php @@ -0,0 +1,222 @@ +basePath = storage_path('agents'); + + if (! File::exists($this->basePath)) { + File::makeDirectory($this->basePath, 0755, true); + } + } + + /** + * Create a new agent status file + */ + public function create(string $agentId, string $swarmId, array $agent): void + { + $data = [ + 'agent_id' => $agentId, + 'swarm_id' => $swarmId, + 'agent' => $agent, + 'status' => AgentStatus::Pending->value, + 'progress' => 0, + 'output' => null, + 'error' => null, + 'created_at' => now()->toIso8601String(), + 'updated_at' => now()->toIso8601String(), + ]; + + $this->writeStatus($agentId, $data); + } + + /** + * Update agent status with arbitrary data + */ + public function update(string $agentId, array $data): void + { + $current = $this->get($agentId); + + if ($current === null) { + return; + } + + $updated = array_merge($current, $data, [ + 'updated_at' => now()->toIso8601String(), + ]); + + $this->writeStatus($agentId, $updated); + } + + /** + * Get agent status + */ + public function get(string $agentId): ?array + { + $path = $this->getStatusPath($agentId); + + if (! File::exists($path)) { + return null; + } + + $content = File::get($path); + + return json_decode($content, true); + } + + /** + * Get all agents + */ + public function getAll(): Collection + { + $agents = collect(); + + if (! File::exists($this->basePath)) { + return $agents; + } + + $files = File::files($this->basePath); + + foreach ($files as $file) { + if ($file->getExtension() !== 'json') { + continue; + } + + $content = File::get($file->getPathname()); + $data = json_decode($content, true); + + if ($data) { + $agents->push($data); + } + } + + return $agents; + } + + /** + * Get all agents belonging to a swarm + */ + public function getBySwarm(string $swarmId): Collection + { + return $this->getAll() + ->filter(fn ($agent) => isset($agent['swarm_id']) && $agent['swarm_id'] === $swarmId); + } + + /** + * Set agent progress percentage + */ + public function setProgress(string $agentId, int $percent): void + { + $this->update($agentId, [ + 'progress' => max(0, min(100, $percent)), + ]); + } + + /** + * Set agent status + */ + public function setStatus(string $agentId, string $status): void + { + $this->update($agentId, [ + 'status' => $status, + ]); + } + + /** + * Mark agent as completed + */ + public function complete(string $agentId, ?string $output = null): void + { + $data = [ + 'status' => AgentStatus::Completed->value, + 'progress' => 100, + 'completed_at' => now()->toIso8601String(), + ]; + + if ($output !== null) { + $data['output'] = $output; + } + + $this->update($agentId, $data); + } + + /** + * Mark agent as failed + */ + public function fail(string $agentId, string $error): void + { + $this->update($agentId, [ + 'status' => AgentStatus::Failed->value, + 'error' => $error, + 'failed_at' => now()->toIso8601String(), + ]); + } + + /** + * Clean up old agent status files + * + * @return int Number of files cleaned up + */ + public function cleanup(int $olderThanMinutes = 60): int + { + $count = 0; + $cutoff = now()->subMinutes($olderThanMinutes); + + if (! File::exists($this->basePath)) { + return 0; + } + + $files = File::files($this->basePath); + + foreach ($files as $file) { + if ($file->getExtension() !== 'json') { + continue; + } + + $content = File::get($file->getPathname()); + $data = json_decode($content, true); + + if (! isset($data['updated_at'])) { + continue; + } + + $updatedAt = \Carbon\Carbon::parse($data['updated_at']); + + if ($updatedAt->lessThan($cutoff)) { + File::delete($file->getPathname()); + $count++; + } + } + + return $count; + } + + /** + * Get the file path for an agent status + */ + private function getStatusPath(string $agentId): string + { + return $this->basePath . DIRECTORY_SEPARATOR . $agentId . '.json'; + } + + /** + * Write status data to file + */ + private function writeStatus(string $agentId, array $data): void + { + $path = $this->getStatusPath($agentId); + + File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } +} diff --git a/database/migrations/2025_12_11_055414_create_agent_swarms_table.php b/database/migrations/2025_12_11_055414_create_agent_swarms_table.php new file mode 100644 index 0000000..e608a5b --- /dev/null +++ b/database/migrations/2025_12_11_055414_create_agent_swarms_table.php @@ -0,0 +1,54 @@ +id(); + $table->string('name'); + $table->text('description')->nullable(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('status')->default('pending'); // pending, running, completed, failed + $table->integer('total_agents')->default(0); + $table->integer('completed_agents')->default(0); + $table->integer('failed_agents')->default(0); + $table->timestamp('started_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->json('metadata')->nullable(); + $table->timestamps(); + }); + + Schema::create('agent_swarm_members', function (Blueprint $table) { + $table->id(); + $table->foreignId('swarm_id')->constrained('agent_swarms')->cascadeOnDelete(); + $table->foreignId('agent_id')->constrained()->cascadeOnDelete(); + $table->string('name'); + $table->string('role')->nullable(); + $table->string('status')->default('pending'); // pending, running, completed, failed + $table->integer('progress')->default(0); + $table->text('current_task')->nullable(); + $table->json('output')->nullable(); + $table->text('error')->nullable(); + $table->timestamp('started_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('agent_swarm_members'); + Schema::dropIfExists('agent_swarms'); + } +}; diff --git a/routes/web.php b/routes/web.php index a789762..6088722 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ name('artifacts.index'); Route::get('artifacts/{artifact}', [ArtifactController::class, 'show'])->name('artifacts.show'); Route::get('artifacts/{artifact}/render', [ArtifactController::class, 'render'])->name('artifacts.render'); + + // Agent Swarm routes + Route::get('agents/swarms', [AgentSwarmController::class, 'index'])->name('agents.swarms.index'); + Route::post('agents/swarms', [AgentSwarmController::class, 'store'])->name('agents.swarms.store'); + Route::get('agents/swarms/{swarm}', [AgentSwarmController::class, 'show'])->name('agents.swarms.show'); + Route::post('agents/swarms/status', [AgentSwarmController::class, 'updateAgentStatus'])->name('agents.swarms.update-status'); }); require __DIR__.'/settings.php';