fantasy-cli 1.2.14 → 1.3.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.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/lib/gsd/agents/communication.rb +260 -0
- data/lib/gsd/agents/swarm.rb +341 -0
- data/lib/gsd/lsp/client.rb +394 -0
- data/lib/gsd/lsp/completion.rb +266 -0
- data/lib/gsd/lsp/diagnostics.rb +259 -0
- data/lib/gsd/lsp/hover.rb +244 -0
- data/lib/gsd/lsp/protocol.rb +434 -0
- data/lib/gsd/lsp/server_manager.rb +290 -0
- data/lib/gsd/lsp/symbols.rb +368 -0
- data/lib/gsd/plugins/api.rb +340 -0
- data/lib/gsd/plugins/hooks.rb +117 -95
- data/lib/gsd/plugins/hot_reload.rb +293 -0
- data/lib/gsd/plugins/registry.rb +273 -0
- data/lib/gsd/tui/agent_panel.rb +182 -0
- data/lib/gsd/tui/animations.rb +320 -0
- data/lib/gsd/tui/app.rb +442 -2
- data/lib/gsd/tui/colors.rb +15 -0
- data/lib/gsd/tui/effects.rb +263 -0
- data/lib/gsd/tui/header.rb +13 -5
- data/lib/gsd/tui/input_box.rb +10 -7
- data/lib/gsd/tui/mouse.rb +388 -0
- data/lib/gsd/tui/persistence.rb +192 -0
- data/lib/gsd/tui/session.rb +273 -0
- data/lib/gsd/tui/status_bar.rb +63 -15
- data/lib/gsd/tui/tab.rb +112 -0
- data/lib/gsd/tui/tab_manager.rb +191 -0
- data/lib/gsd/tui/transitions.rb +262 -0
- data/lib/gsd/version.rb +1 -1
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44d592c83d03126aab712e4f0ffe144439ffde20bda9798974d6d82073222642
|
|
4
|
+
data.tar.gz: 77b9490266d6e8cec2aeda116b9cc6fb68e080d76294db8a9bfa880e0204617a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebfee63b7e36a8502decd98966def7d40371e6109d2e8f0c2b36e7095fe6998d7502543c842c42bbf76a28f379e9042aba69bf408a8a4f2e7c22e643f3d0db1e
|
|
7
|
+
data.tar.gz: f4d69773cc1d1c06bd31b86f3b87d41c5b4ca48cf3bcebed990c40f02ab96c2c0b9cece32c7226d0373f7727f60cfa313c3e55bc2db6792b4de68f8201d21f01
|
data/README.md
CHANGED
|
@@ -17,12 +17,19 @@
|
|
|
17
17
|
|---------|-----------|
|
|
18
18
|
| 🤖 **AI Chat** | REPL interativo com suporte a múltiplos providers (Anthropic, OpenAI, Ollama, LM Studio, OpenRouter) |
|
|
19
19
|
| 📁 **Referências @** | Injete arquivos (`@arquivo.rb`) e diretórios (`@pasta/`) diretamente no contexto da conversa |
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
| ⌨️ **Comandos /** | `/help`, `/model`, `/cost`, `/clear`, `/export`, `/save`, `/restore`, `/tab`, `/plugin`, `/lsp` e mais |
|
|
21
22
|
| 🔮 **Autocomplete** | Sugestões em tempo real para referências de arquivos |
|
|
22
23
|
| 📊 **Git Context** | Branch, status, arquivos modificados e ahead/behind integrados automaticamente |
|
|
23
24
|
| 💰 **Cost Tracking** | Monitoramento de tokens e custo em tempo real com alertas de orçamento |
|
|
24
25
|
| 🏗️ **Project Management** | State, Phase, Roadmap, Milestones, Workstreams |
|
|
25
26
|
| 📝 **Templates** | Geração automática de docs (SUMMARY.md, PLAN.md, VERIFICATION.md) |
|
|
27
|
+
| 🐝 **Multi-Agent Swarm** | Sistema multi-agente com orquestração automática (v1.3.0) |
|
|
28
|
+
| 💾 **Session Persistence** | Save/restore de sessões, checkpoints, auto-save (v1.3.0) |
|
|
29
|
+
| 📑 **Tabs & Panes** | Múltiplas abas independentes no TUI (v1.3.0) |
|
|
30
|
+
| ✨ **Advanced UI** | Animações, transições, mouse support, efeitos visuais (v1.3.0) |
|
|
31
|
+
| 🔌 **Plugin System** | Hot-reload, hooks, sandbox, API para plugins (v1.3.0) |
|
|
32
|
+
| 🔧 **LSP Integration** | Language Server Protocol para Ruby, Python, JS/TS, Go, Rust (v1.3.0) |
|
|
26
33
|
|
|
27
34
|
---
|
|
28
35
|
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Gsd
|
|
7
|
+
module Agents
|
|
8
|
+
# Communication - Sistema de mensagens entre agents
|
|
9
|
+
#
|
|
10
|
+
# Implementa:
|
|
11
|
+
# - Message bus para comunicação assíncrona
|
|
12
|
+
# - Event system para notificações
|
|
13
|
+
# - Message persistence (opcional)
|
|
14
|
+
# - Pub/sub patterns
|
|
15
|
+
class Communication
|
|
16
|
+
attr_reader :channels, :subscriptions, :message_log
|
|
17
|
+
|
|
18
|
+
def initialize(persist_messages: false)
|
|
19
|
+
@channels = {} # channel_name => [messages]
|
|
20
|
+
@subscriptions = {} # channel_name => [agent_ids]
|
|
21
|
+
@message_log = [] # Log de todas as mensagens (se persist=true)
|
|
22
|
+
@persist_messages = persist_messages
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Cria um canal de comunicação
|
|
27
|
+
#
|
|
28
|
+
# @param name [String] Nome do canal
|
|
29
|
+
def create_channel(name)
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@channels[name] ||= []
|
|
32
|
+
@subscriptions[name] ||= []
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Publica mensagem em um canal
|
|
37
|
+
#
|
|
38
|
+
# @param channel [String] Nome do canal
|
|
39
|
+
# @param message [Hash] Conteúdo da mensagem
|
|
40
|
+
# @param sender [String] ID do agente remetente
|
|
41
|
+
def publish(channel:, message:, sender:)
|
|
42
|
+
@mutex.synchronize do
|
|
43
|
+
create_channel(channel) unless @channels.key?(channel)
|
|
44
|
+
|
|
45
|
+
envelope = Message.new(
|
|
46
|
+
channel: channel,
|
|
47
|
+
sender: sender,
|
|
48
|
+
content: message,
|
|
49
|
+
timestamp: Time.now
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@channels[channel] << envelope
|
|
53
|
+
@message_log << envelope if @persist_messages
|
|
54
|
+
|
|
55
|
+
# Notifica subscribers
|
|
56
|
+
notify_subscribers(channel, envelope)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Inscreve um agente em um canal
|
|
61
|
+
#
|
|
62
|
+
# @param agent_id [String] ID do agente
|
|
63
|
+
# @param channel [String] Nome do canal
|
|
64
|
+
def subscribe(agent_id:, channel:)
|
|
65
|
+
@mutex.synchronize do
|
|
66
|
+
create_channel(channel) unless @channels.key?(channel)
|
|
67
|
+
@subscriptions[channel] << agent_id unless @subscriptions[channel].include?(agent_id)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Cancela inscrição de um agente
|
|
72
|
+
#
|
|
73
|
+
# @param agent_id [String] ID do agente
|
|
74
|
+
# @param channel [String] Nome do canal
|
|
75
|
+
def unsubscribe(agent_id:, channel:)
|
|
76
|
+
@mutex.synchronize do
|
|
77
|
+
@subscriptions[channel]&.delete(agent_id)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Obtém mensagens não lidas de um canal para um agente
|
|
82
|
+
#
|
|
83
|
+
# @param agent_id [String] ID do agente
|
|
84
|
+
# @param channel [String] Nome do canal
|
|
85
|
+
# @param since [Time, nil] Timestamp para filtrar mensagens
|
|
86
|
+
# @return [Array<Message>] Mensagens para o agente
|
|
87
|
+
def receive(agent_id:, channel:, since: nil)
|
|
88
|
+
@mutex.synchronize do
|
|
89
|
+
return [] unless @channels.key?(channel)
|
|
90
|
+
|
|
91
|
+
messages = @channels[channel]
|
|
92
|
+
messages = messages.select { |m| m.timestamp > since } if since
|
|
93
|
+
|
|
94
|
+
# Filtra mensagens destinadas a este agente ou broadcast
|
|
95
|
+
messages.select do |m|
|
|
96
|
+
m.recipients.nil? || m.recipients.include?(agent_id) || m.sender != agent_id
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Envia mensagem direta (DM) para agente específico
|
|
102
|
+
#
|
|
103
|
+
# @param to [String] ID do destinatário
|
|
104
|
+
# @param from [String] ID do remetente
|
|
105
|
+
# @param content [Hash] Conteúdo
|
|
106
|
+
def direct_message(to:, from:, content:)
|
|
107
|
+
channel = "dm:#{[to, from].sort.join(':')}"
|
|
108
|
+
|
|
109
|
+
@mutex.synchronize do
|
|
110
|
+
create_channel(channel)
|
|
111
|
+
|
|
112
|
+
envelope = Message.new(
|
|
113
|
+
channel: channel,
|
|
114
|
+
sender: from,
|
|
115
|
+
recipients: [to],
|
|
116
|
+
content: content,
|
|
117
|
+
timestamp: Time.now
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@channels[channel] << envelope
|
|
121
|
+
@message_log << envelope if @persist_messages
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Broadcast para todos agents no canal
|
|
126
|
+
#
|
|
127
|
+
# @param channel [String] Nome do canal
|
|
128
|
+
# @param content [Hash] Conteúdo
|
|
129
|
+
# @param sender [String] ID do remetente
|
|
130
|
+
def broadcast(channel:, content:, sender:)
|
|
131
|
+
publish(channel: channel, message: content, sender: sender)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Obtém histórico de mensagens
|
|
135
|
+
#
|
|
136
|
+
# @param channel [String, nil] Filtrar por canal (nil = todos)
|
|
137
|
+
# @param limit [Integer] Limite de mensagens
|
|
138
|
+
# @return [Array<Message>] Histórico
|
|
139
|
+
def history(channel: nil, limit: 100)
|
|
140
|
+
@mutex.synchronize do
|
|
141
|
+
messages = if channel
|
|
142
|
+
@channels[channel] || []
|
|
143
|
+
else
|
|
144
|
+
@message_log
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
messages.last(limit)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Limpa mensagens antigas de um canal
|
|
152
|
+
#
|
|
153
|
+
# @param channel [String] Nome do canal
|
|
154
|
+
# @param older_than [Time] Remover mensagens mais antigas que
|
|
155
|
+
def cleanup(channel:, older_than:)
|
|
156
|
+
@mutex.synchronize do
|
|
157
|
+
return unless @channels.key?(channel)
|
|
158
|
+
|
|
159
|
+
@channels[channel] = @channels[channel].select { |m| m.timestamp > older_than }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Estatísticas de comunicação
|
|
164
|
+
#
|
|
165
|
+
# @return [Hash] Estatísticas
|
|
166
|
+
def stats
|
|
167
|
+
@mutex.synchronize do
|
|
168
|
+
{
|
|
169
|
+
channels: @channels.count,
|
|
170
|
+
total_messages: @channels.values.sum(&:count),
|
|
171
|
+
subscriptions: @subscriptions.values.sum(&:count),
|
|
172
|
+
persisted_messages: @message_log.count
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
def notify_subscribers(channel, message)
|
|
180
|
+
# Callback para notificar subscribers externos
|
|
181
|
+
# Implementação real usaria callbacks ou event system
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Representação de uma mensagem
|
|
186
|
+
class Message
|
|
187
|
+
attr_reader :id, :channel, :sender, :recipients, :content, :timestamp
|
|
188
|
+
|
|
189
|
+
def initialize(channel:, sender:, content:, timestamp:, recipients: nil)
|
|
190
|
+
@id = SecureRandom.uuid
|
|
191
|
+
@channel = channel
|
|
192
|
+
@sender = sender
|
|
193
|
+
@recipients = recipients
|
|
194
|
+
@content = content
|
|
195
|
+
@timestamp = timestamp
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def to_h
|
|
199
|
+
{
|
|
200
|
+
id: @id,
|
|
201
|
+
channel: @channel,
|
|
202
|
+
sender: @sender,
|
|
203
|
+
recipients: @recipients,
|
|
204
|
+
content: @content,
|
|
205
|
+
timestamp: @timestamp.to_s
|
|
206
|
+
}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def to_json(*args)
|
|
210
|
+
to_h.to_json(*args)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def direct?
|
|
214
|
+
@recipients.present?
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def broadcast?
|
|
218
|
+
@recipients.nil?
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Event System para notificações assíncronas
|
|
223
|
+
class EventBus
|
|
224
|
+
def initialize
|
|
225
|
+
@handlers = {}
|
|
226
|
+
@mutex = Mutex.new
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Registra handler para um evento
|
|
230
|
+
#
|
|
231
|
+
# @param event [Symbol] Nome do evento
|
|
232
|
+
# @param handler [Proc] Handler
|
|
233
|
+
def on(event, &handler)
|
|
234
|
+
@mutex.synchronize do
|
|
235
|
+
@handlers[event] ||= []
|
|
236
|
+
@handlers[event] << handler
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Emite evento
|
|
241
|
+
#
|
|
242
|
+
# @param event [Symbol] Nome do evento
|
|
243
|
+
# @param data [Hash] Dados do evento
|
|
244
|
+
def emit(event, **data)
|
|
245
|
+
handlers = @mutex.synchronize { @handlers[event]&.dup || [] }
|
|
246
|
+
|
|
247
|
+
handlers.each do |handler|
|
|
248
|
+
Thread.new { handler.call(data) }
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Remove todos handlers de um evento
|
|
253
|
+
#
|
|
254
|
+
# @param event [Symbol] Nome do evento
|
|
255
|
+
def off(event)
|
|
256
|
+
@mutex.synchronize { @handlers.delete(event) }
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'observer'
|
|
5
|
+
|
|
6
|
+
module Gsd
|
|
7
|
+
module Agents
|
|
8
|
+
# AgentSwarm - Orquestração multi-agente avançada
|
|
9
|
+
#
|
|
10
|
+
# Gerencia múltiplos agents simultâneos com:
|
|
11
|
+
# - Spawn/monitoramento de agents
|
|
12
|
+
# - Roteamento inteligente de tarefas
|
|
13
|
+
# - Comunicação entre agents
|
|
14
|
+
# - Execução paralela
|
|
15
|
+
# - Auto-recovery
|
|
16
|
+
class Swarm
|
|
17
|
+
include Observable
|
|
18
|
+
|
|
19
|
+
attr_reader :agents, :active_agents, :task_queue, :metrics
|
|
20
|
+
|
|
21
|
+
def initialize(max_concurrent: 5, auto_recovery: true)
|
|
22
|
+
@agents = {} # Hash de agent_id => agent
|
|
23
|
+
@active_agents = [] # Lista de agents ativos
|
|
24
|
+
@task_queue = [] # Fila de tarefas pendentes
|
|
25
|
+
@max_concurrent = max_concurrent
|
|
26
|
+
@auto_recovery = auto_recovery
|
|
27
|
+
@metrics = {
|
|
28
|
+
tasks_completed: 0,
|
|
29
|
+
tasks_failed: 0,
|
|
30
|
+
spawn_count: 0,
|
|
31
|
+
total_runtime: 0.0
|
|
32
|
+
}
|
|
33
|
+
@running = false
|
|
34
|
+
@mutex = Mutex.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Inicia o swarm (loop de orquestração)
|
|
38
|
+
def start
|
|
39
|
+
@running = true
|
|
40
|
+
Thread.new { orchestration_loop }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Para o swarm
|
|
44
|
+
def stop
|
|
45
|
+
@running = false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Cria um novo agent no swarm
|
|
49
|
+
#
|
|
50
|
+
# @param type [Symbol] Tipo do agente (:code, :plan, :debug, etc)
|
|
51
|
+
# @param config [Hash] Configuração do agente
|
|
52
|
+
# @return [String] ID do agente criado
|
|
53
|
+
def spawn(type:, config: {})
|
|
54
|
+
agent_id = generate_agent_id
|
|
55
|
+
agent = Agent.new(
|
|
56
|
+
id: agent_id,
|
|
57
|
+
type: type,
|
|
58
|
+
config: config,
|
|
59
|
+
swarm: self
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@mutex.synchronize do
|
|
63
|
+
@agents[agent_id] = agent
|
|
64
|
+
@active_agents << agent_id
|
|
65
|
+
@metrics[:spawn_count] += 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Notifica observadores
|
|
69
|
+
changed
|
|
70
|
+
notify_observers(:agent_spawned, agent_id, type)
|
|
71
|
+
|
|
72
|
+
agent_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Encerra um agente específico
|
|
76
|
+
#
|
|
77
|
+
# @param agent_id [String] ID do agente
|
|
78
|
+
def terminate(agent_id)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
if agent = @agents.delete(agent_id)
|
|
81
|
+
@active_agents.delete(agent_id)
|
|
82
|
+
agent.stop
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
changed
|
|
87
|
+
notify_observers(:agent_terminated, agent_id)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Roteia uma tarefa para o agente mais adequado
|
|
91
|
+
#
|
|
92
|
+
# @param task [Hash] Descrição da tarefa
|
|
93
|
+
# @return [String] ID do agente que recebeu a tarefa
|
|
94
|
+
def route_task(task)
|
|
95
|
+
router = Router.new(@agents.values)
|
|
96
|
+
best_agent = router.select_agent_for(task)
|
|
97
|
+
|
|
98
|
+
if best_agent
|
|
99
|
+
best_agent.assign_task(task)
|
|
100
|
+
changed
|
|
101
|
+
notify_observers(:task_routed, task, best_agent.id)
|
|
102
|
+
best_agent.id
|
|
103
|
+
else
|
|
104
|
+
# Enfileira se nenhum agente disponível
|
|
105
|
+
@mutex.synchronize { @task_queue << task }
|
|
106
|
+
changed
|
|
107
|
+
notify_observers(:task_queued, task)
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Envia mensagem de um agente para outro
|
|
113
|
+
#
|
|
114
|
+
# @param from [String] ID do agente remetente
|
|
115
|
+
# @param to [String] ID do agente destinatário
|
|
116
|
+
# @param message [Hash] Mensagem
|
|
117
|
+
def send_message(from:, to:, message:)
|
|
118
|
+
return unless target = @agents[to]
|
|
119
|
+
|
|
120
|
+
envelope = {
|
|
121
|
+
from: from,
|
|
122
|
+
to: to,
|
|
123
|
+
message: message,
|
|
124
|
+
timestamp: Time.now,
|
|
125
|
+
id: SecureRandom.uuid
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
target.receive_message(envelope)
|
|
129
|
+
|
|
130
|
+
changed
|
|
131
|
+
notify_observers(:message_sent, envelope)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Broadcast mensagem para todos agents de um tipo
|
|
135
|
+
#
|
|
136
|
+
# @param from [String] ID do agente remetente
|
|
137
|
+
# @param type [Symbol] Tipo de agente destinatário
|
|
138
|
+
# @param message [Hash] Mensagem
|
|
139
|
+
def broadcast(from:, type:, message:)
|
|
140
|
+
targets = @agents.values.select { |a| a.type == type }
|
|
141
|
+
|
|
142
|
+
targets.each do |agent|
|
|
143
|
+
send_message(from: from, to: agent.id, message: message)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Obtém estatísticas do swarm
|
|
148
|
+
#
|
|
149
|
+
# @return [Hash] Métricas atuais
|
|
150
|
+
def stats
|
|
151
|
+
@mutex.synchronize do
|
|
152
|
+
{
|
|
153
|
+
total_agents: @agents.count,
|
|
154
|
+
active_agents: @active_agents.count,
|
|
155
|
+
queued_tasks: @task_queue.count,
|
|
156
|
+
**@metrics
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Lista todos agents de um tipo específico
|
|
162
|
+
#
|
|
163
|
+
# @param type [Symbol, nil] Tipo de agente (nil = todos)
|
|
164
|
+
# @return [Array<Agent>] Lista de agents
|
|
165
|
+
def list(type: nil)
|
|
166
|
+
agents = @agents.values
|
|
167
|
+
agents = agents.select { |a| a.type == type } if type
|
|
168
|
+
agents
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def generate_agent_id
|
|
174
|
+
"agent-#{SecureRandom.uuid[0..7]}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def orchestration_loop
|
|
178
|
+
while @running
|
|
179
|
+
@mutex.synchronize do
|
|
180
|
+
# Processa fila de tarefas
|
|
181
|
+
process_task_queue
|
|
182
|
+
|
|
183
|
+
# Monitora agents e faz recovery se necessário
|
|
184
|
+
monitor_agents if @auto_recovery
|
|
185
|
+
|
|
186
|
+
# Atualiza métricas
|
|
187
|
+
update_metrics
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sleep(0.5) # Intervalo de orquestração
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def process_task_queue
|
|
195
|
+
return if @task_queue.empty?
|
|
196
|
+
return if @active_agents.count >= @max_concurrent
|
|
197
|
+
|
|
198
|
+
# Pega próxima tarefa da fila
|
|
199
|
+
task = @task_queue.shift
|
|
200
|
+
route_task(task)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def monitor_agents
|
|
204
|
+
@active_agents.each do |agent_id|
|
|
205
|
+
agent = @agents[agent_id]
|
|
206
|
+
next unless agent
|
|
207
|
+
|
|
208
|
+
if agent.crashed?
|
|
209
|
+
# Tenta restart
|
|
210
|
+
changed
|
|
211
|
+
notify_observers(:agent_crashed, agent_id)
|
|
212
|
+
|
|
213
|
+
if @auto_recovery
|
|
214
|
+
agent.restart
|
|
215
|
+
changed
|
|
216
|
+
notify_observers(:agent_restarted, agent_id)
|
|
217
|
+
else
|
|
218
|
+
terminate(agent_id)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def update_metrics
|
|
225
|
+
@metrics[:total_runtime] += 0.5
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Agente individual dentro do swarm
|
|
230
|
+
class Agent
|
|
231
|
+
attr_reader :id, :type, :config, :state, :task_count
|
|
232
|
+
|
|
233
|
+
def initialize(id:, type:, config:, swarm:)
|
|
234
|
+
@id = id
|
|
235
|
+
@type = type
|
|
236
|
+
@config = config
|
|
237
|
+
@swarm = swarm
|
|
238
|
+
@state = :idle
|
|
239
|
+
@task_count = 0
|
|
240
|
+
@current_task = nil
|
|
241
|
+
@message_queue = []
|
|
242
|
+
@mutex = Mutex.new
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Atribui uma tarefa ao agente
|
|
246
|
+
def assign_task(task)
|
|
247
|
+
@mutex.synchronize do
|
|
248
|
+
@current_task = task
|
|
249
|
+
@state = :working
|
|
250
|
+
@task_count += 1
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Executa em thread separada
|
|
254
|
+
Thread.new { execute_task(task) }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Recebe mensagem de outro agente
|
|
258
|
+
def receive_message(envelope)
|
|
259
|
+
@mutex.synchronize do
|
|
260
|
+
@message_queue << envelope
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Obtém mensagens pendentes
|
|
265
|
+
def pending_messages
|
|
266
|
+
@mutex.synchronize do
|
|
267
|
+
messages = @message_queue.dup
|
|
268
|
+
@message_queue.clear
|
|
269
|
+
messages
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Para o agente
|
|
274
|
+
def stop
|
|
275
|
+
@mutex.synchronize { @state = :stopped }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Reinicia o agente (recovery)
|
|
279
|
+
def restart
|
|
280
|
+
@mutex.synchronize do
|
|
281
|
+
@state = :idle
|
|
282
|
+
@current_task = nil
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Verifica se agente crashou
|
|
287
|
+
def crashed?
|
|
288
|
+
@state == :crashed
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Reporta crash (para ser chamado se algo der errado)
|
|
292
|
+
def report_crash(error)
|
|
293
|
+
@mutex.synchronize { @state = :crashed }
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
private
|
|
297
|
+
|
|
298
|
+
def execute_task(task)
|
|
299
|
+
# Implementação simulada - na prática, chamaría o provider de AI
|
|
300
|
+
sleep(rand(1.0..3.0)) # Simula processamento
|
|
301
|
+
|
|
302
|
+
@mutex.synchronize do
|
|
303
|
+
@state = :idle
|
|
304
|
+
@current_task = nil
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Notifica swarm que terminou
|
|
308
|
+
@swarm&.changed
|
|
309
|
+
@swarm&.notify_observers(:task_completed, @id, task)
|
|
310
|
+
rescue => e
|
|
311
|
+
report_crash(e)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Router inteligente para distribuir tarefas
|
|
316
|
+
class Router
|
|
317
|
+
def initialize(agents)
|
|
318
|
+
@agents = agents
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Seleciona o melhor agente para uma tarefa
|
|
322
|
+
def select_agent_for(task)
|
|
323
|
+
available = @agents.select { |a| a.state == :idle }
|
|
324
|
+
return nil if available.empty?
|
|
325
|
+
|
|
326
|
+
# Roteamento baseado no tipo de tarefa
|
|
327
|
+
case task[:type]
|
|
328
|
+
when :code, :programming, :debug
|
|
329
|
+
available.select { |a| a.type == :code }.first
|
|
330
|
+
when :plan, :architecture, :design
|
|
331
|
+
available.select { |a| a.type == :plan }.first
|
|
332
|
+
when :debug, :error, :fix
|
|
333
|
+
available.select { |a| a.type == :debug }.first
|
|
334
|
+
else
|
|
335
|
+
# Round-robin para tarefas genéricas
|
|
336
|
+
available.min_by(&:task_count)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|