enhance_swarm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
  3. data/.enhance_swarm/user_patterns.json +37 -0
  4. data/CHANGELOG.md +184 -0
  5. data/LICENSE +21 -0
  6. data/PRODUCTION_TEST_LOG.md +502 -0
  7. data/README.md +905 -0
  8. data/Rakefile +28 -0
  9. data/USAGE_EXAMPLES.md +477 -0
  10. data/examples/enhance_workflow.md +346 -0
  11. data/examples/rails_project.md +253 -0
  12. data/exe/enhance-swarm +30 -0
  13. data/lib/enhance_swarm/additional_commands.rb +299 -0
  14. data/lib/enhance_swarm/agent_communicator.rb +460 -0
  15. data/lib/enhance_swarm/agent_reviewer.rb +283 -0
  16. data/lib/enhance_swarm/agent_spawner.rb +462 -0
  17. data/lib/enhance_swarm/cleanup_manager.rb +245 -0
  18. data/lib/enhance_swarm/cli.rb +1592 -0
  19. data/lib/enhance_swarm/command_executor.rb +78 -0
  20. data/lib/enhance_swarm/configuration.rb +324 -0
  21. data/lib/enhance_swarm/control_agent.rb +307 -0
  22. data/lib/enhance_swarm/dependency_validator.rb +195 -0
  23. data/lib/enhance_swarm/error_recovery.rb +785 -0
  24. data/lib/enhance_swarm/generator.rb +194 -0
  25. data/lib/enhance_swarm/interrupt_handler.rb +512 -0
  26. data/lib/enhance_swarm/logger.rb +106 -0
  27. data/lib/enhance_swarm/mcp_integration.rb +85 -0
  28. data/lib/enhance_swarm/monitor.rb +28 -0
  29. data/lib/enhance_swarm/notification_manager.rb +444 -0
  30. data/lib/enhance_swarm/orchestrator.rb +313 -0
  31. data/lib/enhance_swarm/output_streamer.rb +281 -0
  32. data/lib/enhance_swarm/process_monitor.rb +266 -0
  33. data/lib/enhance_swarm/progress_tracker.rb +215 -0
  34. data/lib/enhance_swarm/project_analyzer.rb +612 -0
  35. data/lib/enhance_swarm/resource_manager.rb +177 -0
  36. data/lib/enhance_swarm/retry_handler.rb +40 -0
  37. data/lib/enhance_swarm/session_manager.rb +247 -0
  38. data/lib/enhance_swarm/signal_handler.rb +95 -0
  39. data/lib/enhance_swarm/smart_defaults.rb +708 -0
  40. data/lib/enhance_swarm/task_integration.rb +150 -0
  41. data/lib/enhance_swarm/task_manager.rb +174 -0
  42. data/lib/enhance_swarm/version.rb +5 -0
  43. data/lib/enhance_swarm/visual_dashboard.rb +555 -0
  44. data/lib/enhance_swarm/web_ui.rb +211 -0
  45. data/lib/enhance_swarm.rb +69 -0
  46. data/setup.sh +86 -0
  47. data/sig/enhance_swarm.rbs +4 -0
  48. data/templates/claude/CLAUDE.md +160 -0
  49. data/templates/claude/MCP.md +117 -0
  50. data/templates/claude/PERSONAS.md +114 -0
  51. data/templates/claude/RULES.md +221 -0
  52. data/test_builtin_functionality.rb +121 -0
  53. data/test_core_components.rb +156 -0
  54. data/test_real_claude_integration.rb +285 -0
  55. data/test_security.rb +150 -0
  56. data/test_smart_defaults.rb +155 -0
  57. data/test_task_integration.rb +173 -0
  58. data/test_web_ui.rb +245 -0
  59. data/web/assets/css/main.css +645 -0
  60. data/web/assets/js/kanban.js +499 -0
  61. data/web/assets/js/main.js +525 -0
  62. data/web/templates/dashboard.html.erb +226 -0
  63. data/web/templates/kanban.html.erb +193 -0
  64. metadata +293 -0
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'json'
5
+ require_relative 'command_executor'
6
+ require_relative 'retry_handler'
7
+ require_relative 'logger'
8
+ require_relative 'cleanup_manager'
9
+ require_relative 'session_manager'
10
+ require_relative 'agent_spawner'
11
+ require_relative 'process_monitor'
12
+ require_relative 'task_integration'
13
+
14
+ module EnhanceSwarm
15
+ class Orchestrator
16
+ def initialize
17
+ @config = EnhanceSwarm.configuration
18
+ @task_manager = TaskManager.new
19
+ @session_manager = SessionManager.new
20
+ @agent_spawner = AgentSpawner.new
21
+ @process_monitor = ProcessMonitor.new
22
+ @task_integration = TaskIntegration.new
23
+ end
24
+
25
+ def enhance(task_id: nil, dry_run: false, follow: false)
26
+ puts '๐ŸŽฏ ENHANCE Protocol Initiated'.colorize(:green)
27
+
28
+ # Step 1: Identify task
29
+ task = task_id ? @task_manager.find_task(task_id) : @task_manager.next_priority_task
30
+ unless task
31
+ puts 'No tasks available in backlog'.colorize(:yellow)
32
+ return
33
+ end
34
+
35
+ puts "๐Ÿ“‹ Task: #{task[:id]} - #{task[:title]}".colorize(:blue)
36
+
37
+ if dry_run
38
+ puts "\n๐Ÿ” Dry run - would execute:".colorize(:yellow)
39
+ show_execution_plan(task)
40
+ return
41
+ end
42
+
43
+ # Step 2: Create or resume session
44
+ unless @session_manager.session_exists?
45
+ @session_manager.create_session(task[:description])
46
+ puts '๐Ÿ“ Created new session'.colorize(:green)
47
+ else
48
+ puts '๐Ÿ“ Resuming existing session'.colorize(:blue)
49
+ end
50
+
51
+ # Step 3: Move task to active
52
+ @task_manager.move_task(task[:id], 'active')
53
+ puts 'โœ… Task moved to active'.colorize(:green)
54
+
55
+ # Step 4: Break down and spawn agents with progress tracking
56
+ agents = break_down_task(task)
57
+ total_tokens = agents.sum { |agent| ProgressTracker.estimate_tokens_for_operation('spawn_agent') }
58
+
59
+ if follow
60
+ spawn_agents_with_streaming(agents)
61
+ else
62
+ ProgressTracker.track(total_steps: 100, estimated_tokens: total_tokens) do |tracker|
63
+ # Spawn agents (0-50% progress)
64
+ spawn_result = spawn_agents(agents, tracker)
65
+
66
+ # Brief monitoring (50-100% progress)
67
+ monitor_with_progress(tracker, @config.monitor_timeout)
68
+ end
69
+ end
70
+
71
+ # Step 5: Continue with other work
72
+ puts "\n๐Ÿ’ก Agents working in background. Check back later with:".colorize(:blue)
73
+ puts ' enhance-swarm status'
74
+ puts ' enhance-swarm monitor'
75
+
76
+ # Return control to user for other work
77
+ end
78
+
79
+ def monitor_with_progress(tracker, timeout_seconds)
80
+ start_time = Time.now
81
+ last_check = start_time
82
+ check_interval = 5 # Check every 5 seconds
83
+
84
+ while (Time.now - start_time) < timeout_seconds
85
+ elapsed = Time.now - start_time
86
+ progress = 50 + (elapsed / timeout_seconds * 50).to_i # 50-100% progress
87
+
88
+ # Get current status using built-in monitor
89
+ status = @process_monitor.status
90
+ active_count = status[:active_agents]
91
+
92
+ tracker.set_progress(progress,
93
+ message: "Monitoring #{active_count} active agents...",
94
+ operation: 'monitor',
95
+ details: {
96
+ active_agents: active_count,
97
+ elapsed: "#{elapsed.round}s"
98
+ })
99
+
100
+ sleep(check_interval)
101
+ end
102
+
103
+ tracker.set_progress(100, message: "Monitoring complete - agents running in background")
104
+ end
105
+
106
+ def spawn_agents_with_streaming(agents)
107
+ puts "\n๐Ÿค– Spawning #{agents.count} agents with live output...".colorize(:yellow)
108
+
109
+ # Use built-in agent spawner
110
+ spawned_agents = @agent_spawner.spawn_multiple_agents(agents)
111
+
112
+ return if spawned_agents.empty?
113
+
114
+ puts "\n๐Ÿ”ด Live output streaming started for #{spawned_agents.count} agents. Press Ctrl+C to stop watching.\n".colorize(:green)
115
+
116
+ # Start streaming output for all agents
117
+ begin
118
+ OutputStreamer.stream_agents(spawned_agents)
119
+ rescue NameError
120
+ # Fallback: Use built-in monitoring if OutputStreamer doesn't exist
121
+ puts "๐Ÿ” Switching to built-in monitoring...".colorize(:blue)
122
+ @process_monitor.watch(interval: 3)
123
+ end
124
+ end
125
+
126
+ def spawn_single(task:, role:, worktree:)
127
+ operation_id = "spawn_#{role}_#{Time.now.to_i}"
128
+ Logger.log_operation(operation_id, 'started', { role: role, worktree: worktree })
129
+
130
+ puts "๐Ÿš€ Spawning #{role} agent...".colorize(:yellow)
131
+
132
+ begin
133
+ # Use built-in agent spawner
134
+ result = @agent_spawner.spawn_agent(
135
+ role: role,
136
+ task: task,
137
+ worktree: worktree && @config.worktree_enabled
138
+ )
139
+
140
+ if result
141
+ puts 'โœ… Agent spawned successfully'.colorize(:green)
142
+ puts " ๐Ÿ“Š PID: #{result[:pid]}, Role: #{result[:role]}".colorize(:blue)
143
+ if result[:worktree_path]
144
+ puts " ๐Ÿ“Š Worktree: #{File.basename(result[:worktree_path])}".colorize(:blue)
145
+ end
146
+
147
+ Logger.log_operation(operation_id, 'success', { role: role, pid: result[:pid] })
148
+
149
+ # Wait a moment and check if the process is still running
150
+ sleep(1)
151
+ begin
152
+ Process.getpgid(result[:pid])
153
+ puts " ๐Ÿ“Š Agent process confirmed running".colorize(:green)
154
+ rescue Errno::ESRCH
155
+ puts " ๐Ÿ“Š Agent process completed quickly".colorize(:yellow)
156
+ end
157
+
158
+ result[:pid]
159
+ else
160
+ puts "โŒ Failed to spawn agent".colorize(:red)
161
+ puts " ๐Ÿ’ก Try running with ENHANCE_SWARM_DEBUG=true for details".colorize(:yellow)
162
+ Logger.log_operation(operation_id, 'failed', { role: role, error: 'Spawn failed' })
163
+ false
164
+ end
165
+ rescue StandardError => e
166
+ puts "โŒ Failed to spawn agent: #{e.message}".colorize(:red)
167
+ Logger.log_operation(operation_id, 'failed', { role: role, error: e.message })
168
+ false
169
+ end
170
+ end
171
+
172
+ def get_task_management_data
173
+ @task_integration.get_kanban_data
174
+ end
175
+
176
+ def setup_task_management
177
+ @task_integration.setup_task_management
178
+ end
179
+
180
+ private
181
+
182
+ def break_down_task(task)
183
+ agents = []
184
+
185
+ # Analyze task description to determine needed agents
186
+ desc = task[:description].downcase
187
+
188
+ # Always include QA for any feature work
189
+ needs_qa = desc.match?(/feature|implement|add|create|build/)
190
+
191
+ # Check what types of work are needed
192
+ needs_ui = desc.match?(/ui|interface|design|template|email|view|component/)
193
+ needs_backend = desc.match?(/model|database|api|service|migration|business logic/)
194
+ needs_frontend = desc.match?(/controller|javascript|turbo|stimulus|form/)
195
+
196
+ # Build agent list based on analysis
197
+ agents << { role: 'ux', task: extract_ux_work(task) } if needs_ui
198
+ agents << { role: 'backend', task: extract_backend_work(task) } if needs_backend
199
+ agents << { role: 'frontend', task: extract_frontend_work(task) } if needs_frontend
200
+ agents << { role: 'qa', task: extract_qa_work(task) } if needs_qa
201
+
202
+ # If no specific needs detected, spawn a general agent
203
+ agents << { role: 'general', task: task[:description] } if agents.empty?
204
+
205
+ agents
206
+ end
207
+
208
+ def spawn_agents(agents, tracker = nil)
209
+ if tracker
210
+ tracker.update_status("Spawning #{agents.count} agents...", operation: 'spawn_agents')
211
+ else
212
+ puts "\n๐Ÿค– Spawning #{agents.count} agents...".colorize(:yellow)
213
+ end
214
+
215
+ spawned_agents = []
216
+ failed_agents = []
217
+ total_estimated_tokens = agents.sum { |agent| ProgressTracker.estimate_tokens_for_operation('spawn_agent') }
218
+
219
+ agents.each_with_index do |agent, index|
220
+ # Update progress if tracker available
221
+ if tracker
222
+ progress = (index.to_f / agents.count * 50).to_i # First 50% for spawning
223
+ tracker.set_progress(progress,
224
+ message: "Spawning #{agent[:role]} agent...",
225
+ agent: agent[:role],
226
+ operation: 'spawn')
227
+ end
228
+
229
+ # Add jitter to prevent resource contention
230
+ sleep(2 + rand(0..2)) if index > 0
231
+
232
+ # Use built-in agent spawner
233
+ result = @agent_spawner.spawn_agent(
234
+ role: agent[:role],
235
+ task: agent[:task],
236
+ worktree: true
237
+ )
238
+
239
+ if result
240
+ spawned_agents << { **agent, pid: result[:pid] }
241
+ tracker&.add_tokens(ProgressTracker.estimate_tokens_for_operation('spawn_agent'))
242
+ else
243
+ failed_agents << agent
244
+ end
245
+ end
246
+
247
+ if tracker
248
+ tracker.set_progress(50,
249
+ message: "All agents spawned, monitoring...",
250
+ operation: 'monitor')
251
+ elsif failed_agents.empty?
252
+ puts 'โœ… All agents spawned'.colorize(:green)
253
+ else
254
+ puts "โš ๏ธ #{spawned_agents.size}/#{agents.size} agents spawned successfully".colorize(:yellow)
255
+ Logger.warn("Failed to spawn agents: #{failed_agents.map { |a| a[:role] }.join(', ')}")
256
+ end
257
+
258
+ { spawned: spawned_agents, failed: failed_agents }
259
+ end
260
+
261
+
262
+ def extract_ux_work(task)
263
+ "Design and implement UI/UX for: #{task[:description]}"
264
+ end
265
+
266
+ def extract_backend_work(task)
267
+ "Implement backend logic for: #{task[:description]}"
268
+ end
269
+
270
+ def extract_frontend_work(task)
271
+ "Implement frontend integration for: #{task[:description]}"
272
+ end
273
+
274
+ def extract_qa_work(task)
275
+ "Write comprehensive tests for: #{task[:description]}"
276
+ end
277
+
278
+ def sanitize_command(command)
279
+ # Basic command sanitization - remove shell metacharacters
280
+ command.to_s.gsub(/[;&|`$\\]/, '').strip
281
+ end
282
+
283
+ def sanitize_argument(arg)
284
+ # Escape shell metacharacters in arguments
285
+ require 'shellwords'
286
+ Shellwords.escape(arg.to_s)
287
+ end
288
+
289
+ def sanitize_role(role)
290
+ # Only allow known safe roles
291
+ allowed_roles = %w[ux backend frontend qa general]
292
+ role = role.to_s.downcase.strip
293
+ allowed_roles.include?(role) ? role : 'general'
294
+ end
295
+
296
+ def show_execution_plan(task)
297
+ agents = break_down_task(task)
298
+
299
+ puts "Would spawn #{agents.count} agents:".colorize(:blue)
300
+ agents.each do |agent|
301
+ puts " - #{agent[:role].upcase}: #{agent[:task]}"
302
+ end
303
+
304
+ puts "\nActions that would be executed:"
305
+ puts " 1. Create or resume agent session"
306
+ puts " 2. #{sanitize_command(@config.task_move_command)} #{sanitize_argument(task[:id])} active"
307
+ agents.each_with_index do |agent, index|
308
+ puts " #{index + 3}. Spawn #{sanitize_role(agent[:role])} agent with git worktree"
309
+ end
310
+ puts " #{agents.count + 3}. Monitor agent progress"
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+ require 'pty'
5
+
6
+ module EnhanceSwarm
7
+ class OutputStreamer
8
+ attr_reader :agent_outputs, :active_agents
9
+
10
+ def initialize(max_lines: 10)
11
+ @max_lines = max_lines
12
+ @agent_outputs = {}
13
+ @active_agents = {}
14
+ @output_mutex = Mutex.new
15
+ @display_thread = nil
16
+ @running = false
17
+ end
18
+
19
+ def start_streaming
20
+ @running = true
21
+ @display_thread = Thread.new { display_loop }
22
+ end
23
+
24
+ def stop_streaming
25
+ @running = false
26
+ @display_thread&.join
27
+ clear_display
28
+ end
29
+
30
+ def add_agent(agent_id, process_pid, role: 'agent')
31
+ @output_mutex.synchronize do
32
+ @active_agents[agent_id] = {
33
+ pid: process_pid,
34
+ role: role,
35
+ start_time: Time.now,
36
+ status: 'running'
37
+ }
38
+ @agent_outputs[agent_id] = {
39
+ lines: [],
40
+ last_update: Time.now
41
+ }
42
+ end
43
+
44
+ # Start monitoring this agent's output (in real implementation)
45
+ # start_agent_monitor(agent_id, process_pid)
46
+ end
47
+
48
+ def remove_agent(agent_id, status: 'completed')
49
+ @output_mutex.synchronize do
50
+ if @active_agents[agent_id]
51
+ @active_agents[agent_id][:status] = status
52
+ @active_agents[agent_id][:end_time] = Time.now
53
+ end
54
+ end
55
+ end
56
+
57
+ def add_output_line(agent_id, line)
58
+ @output_mutex.synchronize do
59
+ return unless @agent_outputs[agent_id]
60
+
61
+ # Clean and format the line
62
+ clean_line = clean_output_line(line)
63
+ return if clean_line.empty?
64
+
65
+ @agent_outputs[agent_id][:lines] << {
66
+ text: clean_line,
67
+ timestamp: Time.now
68
+ }
69
+
70
+ # Keep only recent lines
71
+ if @agent_outputs[agent_id][:lines].length > @max_lines
72
+ @agent_outputs[agent_id][:lines].shift
73
+ end
74
+
75
+ @agent_outputs[agent_id][:last_update] = Time.now
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def display_loop
82
+ while @running
83
+ render_display
84
+ sleep(0.1) # 10 FPS update rate
85
+ end
86
+ end
87
+
88
+ def render_display
89
+ @output_mutex.synchronize do
90
+ clear_display
91
+
92
+ # Show active agents summary
93
+ puts build_agents_summary
94
+ puts
95
+
96
+ # Show live output for each agent
97
+ @active_agents.each do |agent_id, agent_info|
98
+ next unless agent_info[:status] == 'running'
99
+ render_agent_output(agent_id, agent_info)
100
+ end
101
+
102
+ # Show completed agents summary
103
+ completed = @active_agents.select { |_, info| info[:status] != 'running' }
104
+ if completed.any?
105
+ puts build_completed_summary(completed)
106
+ end
107
+ end
108
+ end
109
+
110
+ def build_agents_summary
111
+ running = @active_agents.count { |_, info| info[:status] == 'running' }
112
+ completed = @active_agents.count { |_, info| info[:status] == 'completed' }
113
+ failed = @active_agents.count { |_, info| info[:status] == 'failed' }
114
+
115
+ summary = "๐Ÿค– Agents: #{running} running"
116
+ summary += ", #{completed} completed".colorize(:green) if completed > 0
117
+ summary += ", #{failed} failed".colorize(:red) if failed > 0
118
+
119
+ summary
120
+ end
121
+
122
+ def render_agent_output(agent_id, agent_info)
123
+ role = agent_info[:role]
124
+ elapsed = Time.now - agent_info[:start_time]
125
+
126
+ # Agent header
127
+ puts "โ”Œโ”€ #{role_icon(role)} #{role.upcase} Agent (#{format_duration(elapsed)}) #{agent_id[0..7]} โ”€".ljust(60, 'โ”€') + 'โ”'
128
+
129
+ # Recent output lines
130
+ output_data = @agent_outputs[agent_id]
131
+ if output_data[:lines].any?
132
+ output_data[:lines].last(@max_lines).each do |line_data|
133
+ age = Time.now - line_data[:timestamp]
134
+ age_indicator = age < 2 ? 'โ—'.colorize(:green) : age < 10 ? 'โ—'.colorize(:yellow) : 'โ—'.colorize(:light_black)
135
+ puts "โ”‚ #{age_indicator} #{line_data[:text].ljust(55)[0..55]} โ”‚"
136
+ end
137
+ else
138
+ puts "โ”‚ #{' ' * 57} โ”‚"
139
+ puts "โ”‚ #{'No output yet...'.colorize(:light_black).ljust(57)} โ”‚"
140
+ end
141
+
142
+ puts "โ””#{'โ”€' * 58}โ”˜"
143
+ puts
144
+ end
145
+
146
+ def build_completed_summary(completed_agents)
147
+ summary = "\n๐Ÿ“‹ Completed:"
148
+ completed_agents.each do |agent_id, info|
149
+ duration = info[:end_time] - info[:start_time]
150
+ status_icon = info[:status] == 'completed' ? 'โœ…' : 'โŒ'
151
+ summary += "\n #{status_icon} #{info[:role]} (#{format_duration(duration)})"
152
+ end
153
+ summary
154
+ end
155
+
156
+ def start_agent_monitor(agent_id, process_pid)
157
+ Thread.new do
158
+ begin
159
+ # Monitor the process output by tailing its log file or stdout
160
+ # This is a simplified version - in reality you'd need to capture
161
+ # the actual agent's stdout/stderr
162
+ monitor_agent_process(agent_id, process_pid)
163
+ rescue StandardError => e
164
+ Logger.error("Failed to monitor agent #{agent_id}: #{e.message}")
165
+ end
166
+ end
167
+ end
168
+
169
+ def monitor_agent_process(agent_id, process_pid)
170
+ # Check if process is still running
171
+ loop do
172
+ break unless process_running?(process_pid)
173
+
174
+ # Try to read from agent's output (this would need actual implementation)
175
+ # For now, simulate some output
176
+ if rand < 0.3 # 30% chance of output per cycle
177
+ simulate_agent_output(agent_id)
178
+ end
179
+
180
+ sleep(1)
181
+ end
182
+
183
+ # Process ended
184
+ remove_agent(agent_id, status: 'completed')
185
+ rescue StandardError => e
186
+ remove_agent(agent_id, status: 'failed')
187
+ add_output_line(agent_id, "โŒ Agent failed: #{e.message}")
188
+ end
189
+
190
+ def process_running?(pid)
191
+ Process.kill(0, pid)
192
+ true
193
+ rescue Errno::ESRCH
194
+ false
195
+ rescue Errno::EPERM
196
+ true # Process exists but we can't signal it
197
+ end
198
+
199
+ def simulate_agent_output(agent_id)
200
+ # This simulates agent output - replace with actual output capture
201
+ sample_outputs = [
202
+ "๐Ÿ” Analyzing project structure...",
203
+ "๐Ÿ“ Reading file: #{['app/models/user.rb', 'config/routes.rb', 'spec/models/user_spec.rb'].sample}",
204
+ "โœ… Found existing #{['authentication', 'user model', 'routes', 'tests'].sample}",
205
+ "๐Ÿ”ง Generating #{['controller', 'migration', 'view', 'test'].sample}...",
206
+ "๐Ÿ“ Writing #{['spec/requests/auth_spec.rb', 'app/controllers/sessions_controller.rb', 'app/views/sessions/new.html.erb'].sample}",
207
+ "๐Ÿƒ Running #{['rspec', 'rubocop', 'tests'].sample}...",
208
+ "๐ŸŽฏ Implementing #{['login logic', 'validation', 'error handling'].sample}...",
209
+ "๐Ÿ”’ Adding security #{['validations', 'sanitization', 'authentication'].sample}..."
210
+ ]
211
+
212
+ add_output_line(agent_id, sample_outputs.sample)
213
+ end
214
+
215
+ def clean_output_line(line)
216
+ # Remove ANSI color codes and control characters
217
+ line.to_s
218
+ .gsub(/\e\[[0-9;]*m/, '') # Remove ANSI color codes
219
+ .gsub(/\r\n?/, '') # Remove carriage returns
220
+ .strip
221
+ .slice(0, 55) # Limit length
222
+ end
223
+
224
+ def role_icon(role)
225
+ icons = {
226
+ 'backend' => '๐Ÿ”ง',
227
+ 'frontend' => '๐ŸŽจ',
228
+ 'ux' => 'โœจ',
229
+ 'qa' => '๐Ÿงช',
230
+ 'general' => '๐Ÿค–'
231
+ }
232
+ icons[role.to_s] || '๐Ÿค–'
233
+ end
234
+
235
+ def format_duration(seconds)
236
+ if seconds < 60
237
+ "#{seconds.round}s"
238
+ elsif seconds < 3600
239
+ "#{(seconds / 60).round}m"
240
+ else
241
+ "#{(seconds / 3600).round(1)}h"
242
+ end
243
+ end
244
+
245
+ def clear_display
246
+ # Move cursor to top and clear screen
247
+ print "\e[H\e[2J"
248
+ end
249
+
250
+ # Class methods for easy usage
251
+ def self.stream_agents(agents)
252
+ streamer = new
253
+
254
+ begin
255
+ streamer.start_streaming
256
+
257
+ # Add all agents to streamer
258
+ agents.each do |agent|
259
+ streamer.add_agent(
260
+ agent[:id] || "#{agent[:role]}-#{Time.now.to_i}",
261
+ agent[:pid],
262
+ role: agent[:role]
263
+ )
264
+ end
265
+
266
+ # Wait for user interrupt or all agents to complete
267
+ trap('INT') { streamer.stop_streaming; exit }
268
+
269
+ loop do
270
+ active_count = streamer.active_agents.count { |_, info| info[:status] == 'running' }
271
+ break if active_count == 0
272
+
273
+ sleep(1)
274
+ end
275
+
276
+ ensure
277
+ streamer.stop_streaming
278
+ end
279
+ end
280
+ end
281
+ end