bimmo-cli 1.2.4 → 2.0.3
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.
- package/package.json +5 -2
- package/src/config.js +48 -1
- package/src/interface.js +168 -88
- package/src/orchestrator.js +84 -0
- package/src/providers/anthropic.js +6 -6
- package/src/providers/base.js +2 -2
- package/src/providers/gemini.js +6 -7
- package/src/providers/grok.js +4 -3
- package/src/providers/ollama.js +4 -4
- package/src/providers/openai.js +9 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bimmo-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "🌿
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "🌿 Plataforma de IA universal com modo Normal, Agentes Especialistas e Enxames (Swarms). Suporte a Auto-Edit, Contexto Inteligente e interrupção inteligente.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bimmo": "bin/bimmo"
|
|
7
7
|
},
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"grok",
|
|
16
16
|
"deepseek",
|
|
17
17
|
"openrouter",
|
|
18
|
+
"zai",
|
|
18
19
|
"agent",
|
|
20
|
+
"swarm",
|
|
21
|
+
"orchestration",
|
|
19
22
|
"multimodal",
|
|
20
23
|
"terminal"
|
|
21
24
|
],
|
package/src/config.js
CHANGED
|
@@ -29,6 +29,7 @@ export async function configure() {
|
|
|
29
29
|
choices: [
|
|
30
30
|
{ name: 'Criar novo perfil de IA', value: 'create' },
|
|
31
31
|
{ name: 'Selecionar perfil ativo', value: 'select' },
|
|
32
|
+
{ name: 'Gerenciar Agentes Especialistas', value: 'agents' },
|
|
32
33
|
{ name: 'Configurar chave Tavily', value: 'tavily' },
|
|
33
34
|
{ name: 'Sair', value: 'exit' }
|
|
34
35
|
]
|
|
@@ -37,6 +38,8 @@ export async function configure() {
|
|
|
37
38
|
|
|
38
39
|
if (action === 'exit') return;
|
|
39
40
|
|
|
41
|
+
if (action === 'agents') return configureAgents();
|
|
42
|
+
|
|
40
43
|
if (action === 'tavily') {
|
|
41
44
|
const { tavilyKey } = await inquirer.prompt([{
|
|
42
45
|
type: 'input',
|
|
@@ -74,7 +77,7 @@ export async function configure() {
|
|
|
74
77
|
{
|
|
75
78
|
type: 'input',
|
|
76
79
|
name: 'profileName',
|
|
77
|
-
message: 'Dê um nome para este perfil
|
|
80
|
+
message: 'Dê um nome para este perfil:',
|
|
78
81
|
validate: i => i.length > 0 || 'Nome obrigatório'
|
|
79
82
|
},
|
|
80
83
|
{
|
|
@@ -120,6 +123,50 @@ export async function configure() {
|
|
|
120
123
|
console.log(chalk.green(`\n✅ Perfil "${answers.profileName}" criado e ativado!`));
|
|
121
124
|
}
|
|
122
125
|
|
|
126
|
+
async function configureAgents() {
|
|
127
|
+
const agents = config.get('agents') || {};
|
|
128
|
+
const profiles = config.get('profiles') || {};
|
|
129
|
+
const profileList = Object.keys(profiles);
|
|
130
|
+
|
|
131
|
+
const { action } = await inquirer.prompt([{
|
|
132
|
+
type: 'list',
|
|
133
|
+
name: 'action',
|
|
134
|
+
message: 'Gerenciar Agentes:',
|
|
135
|
+
choices: ['Criar Agente', 'Listar Agentes', 'Remover Agente', 'Voltar']
|
|
136
|
+
}]);
|
|
137
|
+
|
|
138
|
+
if (action === 'Voltar') return configure();
|
|
139
|
+
|
|
140
|
+
if (action === 'Criar Agente') {
|
|
141
|
+
if (profileList.length === 0) {
|
|
142
|
+
console.log(chalk.red('Crie um perfil de IA primeiro.'));
|
|
143
|
+
return configure();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const answers = await inquirer.prompt([
|
|
147
|
+
{ type: 'input', name: 'name', message: 'Nome do Agente (ex: Arquiteto, Revisor):' },
|
|
148
|
+
{ type: 'list', name: 'profile', message: 'Qual perfil este agente usará?', choices: profileList },
|
|
149
|
+
{ type: 'input', name: 'modelOverride', message: 'Sobrescrever modelo (vazio para manter o do perfil):' },
|
|
150
|
+
{ type: 'list', name: 'mode', message: 'Modo padrão:', choices: ['chat', 'plan', 'edit'] },
|
|
151
|
+
{ type: 'editor', name: 'role', message: 'Descreva a Task/Papel deste agente (System Prompt):' }
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
agents[answers.name] = answers;
|
|
155
|
+
config.set('agents', agents);
|
|
156
|
+
console.log(chalk.green(`✓ Agente "${answers.name}" criado!`));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (action === 'Listar Agentes') {
|
|
160
|
+
console.log(chalk.blue('\nAgentes Configurados:'));
|
|
161
|
+
Object.keys(agents).forEach(name => {
|
|
162
|
+
console.log(`- ${chalk.bold(name)} [${agents[name].profile}] (Modo: ${agents[name].mode})`);
|
|
163
|
+
});
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return configureAgents();
|
|
168
|
+
}
|
|
169
|
+
|
|
123
170
|
export function getConfig() {
|
|
124
171
|
return config.store;
|
|
125
172
|
}
|
package/src/interface.js
CHANGED
|
@@ -7,10 +7,12 @@ import ora from 'ora';
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import mime from 'mime-types';
|
|
10
|
+
import readline from 'readline';
|
|
10
11
|
|
|
11
12
|
import { getConfig, configure, updateActiveModel, switchProfile } from './config.js';
|
|
12
13
|
import { createProvider } from './providers/factory.js';
|
|
13
14
|
import { getProjectContext } from './project-context.js';
|
|
15
|
+
import { SwarmOrchestrator } from './orchestrator.js';
|
|
14
16
|
|
|
15
17
|
marked.use(new TerminalRenderer({
|
|
16
18
|
heading: chalk.hex('#c084fc').bold,
|
|
@@ -23,7 +25,10 @@ const gray = chalk.gray;
|
|
|
23
25
|
const bold = chalk.bold;
|
|
24
26
|
const yellow = chalk.yellow;
|
|
25
27
|
|
|
26
|
-
let currentMode = 'chat';
|
|
28
|
+
let currentMode = 'chat';
|
|
29
|
+
let activePersona = null;
|
|
30
|
+
let exitCounter = 0;
|
|
31
|
+
let exitTimer = null;
|
|
27
32
|
|
|
28
33
|
async function processInput(input) {
|
|
29
34
|
const parts = input.split(' ');
|
|
@@ -91,10 +96,11 @@ async function processInput(input) {
|
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
function getModeStyle() {
|
|
99
|
+
const personaLabel = activePersona ? `[${activePersona.toUpperCase()}] ` : '';
|
|
94
100
|
switch (currentMode) {
|
|
95
|
-
case 'plan': return yellow.bold(
|
|
96
|
-
case 'edit': return chalk.red.bold(
|
|
97
|
-
default: return lavender.bold(
|
|
101
|
+
case 'plan': return yellow.bold(`${personaLabel}[PLAN] `);
|
|
102
|
+
case 'edit': return chalk.red.bold(`${personaLabel}[EDIT] `);
|
|
103
|
+
default: return lavender.bold(`${personaLabel}[CHAT] `);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
|
|
@@ -109,77 +115,120 @@ export async function startInteractive() {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
let provider = createProvider(config);
|
|
112
|
-
const
|
|
118
|
+
const orchestrator = new SwarmOrchestrator(config);
|
|
119
|
+
let messages = [];
|
|
120
|
+
|
|
121
|
+
const resetMessages = () => {
|
|
122
|
+
messages = [];
|
|
123
|
+
const projectContext = getProjectContext();
|
|
124
|
+
messages.push({ role: 'system', content: projectContext });
|
|
125
|
+
if (activePersona) {
|
|
126
|
+
const agent = (config.agents || {})[activePersona];
|
|
127
|
+
if (agent) {
|
|
128
|
+
messages.push({ role: 'system', content: `Sua persona atual é: ${agent.name}. Sua tarefa: ${agent.role}` });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
113
132
|
|
|
114
|
-
|
|
115
|
-
const projectContext = getProjectContext();
|
|
116
|
-
messages.push({
|
|
117
|
-
role: 'system',
|
|
118
|
-
content: projectContext
|
|
119
|
-
});
|
|
133
|
+
resetMessages();
|
|
120
134
|
|
|
121
135
|
console.clear();
|
|
122
136
|
console.log(lavender(figlet.textSync('bimmo')));
|
|
123
137
|
console.log(lavender('─'.repeat(60)));
|
|
124
|
-
console.log(green(` Perfil
|
|
138
|
+
console.log(green(` Perfil: ${bold(config.activeProfile || 'Padrão')} • IA: ${bold(config.provider.toUpperCase())}`));
|
|
125
139
|
console.log(green(` Modelo: ${bold(config.model)}`));
|
|
126
|
-
console.log(gray(' /chat | /plan | /edit | /
|
|
140
|
+
console.log(gray(' /chat | /plan | /edit | /swarm | /use [agente] | /help'));
|
|
127
141
|
console.log(lavender('─'.repeat(60)) + '\n');
|
|
128
142
|
|
|
129
|
-
console.log(lavender('👋 Olá!
|
|
143
|
+
console.log(lavender('👋 Olá! Estou pronto. No que posso ajudar?\n'));
|
|
144
|
+
|
|
145
|
+
// Handler Global de SIGINT para o modo ocioso (Idle)
|
|
146
|
+
process.on('SIGINT', () => {
|
|
147
|
+
exitCounter++;
|
|
148
|
+
if (exitCounter === 1) {
|
|
149
|
+
console.log(gray('\n(Pressione Ctrl+C novamente para sair)'));
|
|
150
|
+
if (exitTimer) clearTimeout(exitTimer);
|
|
151
|
+
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
152
|
+
} else {
|
|
153
|
+
console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
readline.emitKeypressEvents(process.stdin);
|
|
159
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
130
160
|
|
|
131
161
|
while (true) {
|
|
132
162
|
const modeIndicator = getModeStyle();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
let input;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const answers = await inquirer.prompt([
|
|
167
|
+
{
|
|
168
|
+
type: 'input',
|
|
169
|
+
name: 'input',
|
|
170
|
+
message: modeIndicator + green('Você'),
|
|
171
|
+
prefix: '→',
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
input = answers.input;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
// Inquirer joga erro no Ctrl+C se não for tratado
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
141
179
|
|
|
142
180
|
const rawInput = input.trim();
|
|
143
181
|
const cmd = rawInput.toLowerCase();
|
|
144
182
|
|
|
145
183
|
if (cmd === '/exit' || cmd === 'exit' || cmd === 'sair') {
|
|
146
184
|
console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
|
|
147
|
-
|
|
185
|
+
process.exit(0);
|
|
148
186
|
}
|
|
149
187
|
|
|
150
188
|
if (cmd === '/chat') { currentMode = 'chat'; console.log(lavender('✓ Modo CHAT.\n')); continue; }
|
|
151
189
|
if (cmd === '/plan') { currentMode = 'plan'; console.log(yellow('✓ Modo PLAN.\n')); continue; }
|
|
152
190
|
if (cmd === '/edit') { currentMode = 'edit'; console.log(chalk.red('⚠️ Modo EDIT.\n')); continue; }
|
|
153
191
|
|
|
154
|
-
// /switch [perfil] -> Troca Perfil + Chave + Provedor + Modelo instantaneamente
|
|
155
192
|
if (cmd.startsWith('/switch ')) {
|
|
156
193
|
const profileName = rawInput.split(' ')[1];
|
|
157
194
|
if (profileName && switchProfile(profileName)) {
|
|
158
|
-
config = getConfig();
|
|
159
|
-
provider = createProvider(config);
|
|
160
|
-
console.log(green(`\n✓
|
|
161
|
-
|
|
162
|
-
} else {
|
|
163
|
-
console.log(chalk.red(`\n✖ Perfil "${profileName}" não encontrado.\n`));
|
|
195
|
+
config = getConfig();
|
|
196
|
+
provider = createProvider(config);
|
|
197
|
+
console.log(green(`\n✓ Perfil "${bold(profileName)}" ativado!`));
|
|
198
|
+
continue;
|
|
164
199
|
}
|
|
200
|
+
console.log(chalk.red(`\n✖ Perfil não encontrado.\n`));
|
|
165
201
|
continue;
|
|
166
202
|
}
|
|
167
203
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
if (cmd.startsWith('/use ')) {
|
|
205
|
+
const agentName = rawInput.split(' ')[1];
|
|
206
|
+
const agents = config.agents || {};
|
|
207
|
+
if (agentName === 'normal' || agentName === 'default') {
|
|
208
|
+
activePersona = null;
|
|
209
|
+
console.log(lavender(`\n✓ Voltando para o Modo Normal.\n`));
|
|
210
|
+
resetMessages();
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (agents[agentName]) {
|
|
214
|
+
activePersona = agentName;
|
|
215
|
+
const agent = agents[agentName];
|
|
216
|
+
if (switchProfile(agent.profile)) {
|
|
217
|
+
config = getConfig();
|
|
218
|
+
provider = createProvider(config);
|
|
219
|
+
}
|
|
220
|
+
currentMode = agent.mode || 'chat';
|
|
221
|
+
console.log(green(`\n✓ Agora você está falando com o Agente: ${bold(agentName)}`));
|
|
222
|
+
console.log(gray(` Task: ${agent.role.substring(0, 100)}...\n`));
|
|
223
|
+
resetMessages();
|
|
224
|
+
} else {
|
|
225
|
+
console.log(chalk.red(`\n✖ Agente "${agentName}" não encontrado.\n`));
|
|
176
226
|
}
|
|
177
227
|
continue;
|
|
178
228
|
}
|
|
179
229
|
|
|
180
230
|
if (cmd === '/clear') {
|
|
181
|
-
|
|
182
|
-
messages.push({ role: 'system', content: getProjectContext() });
|
|
231
|
+
resetMessages();
|
|
183
232
|
console.clear();
|
|
184
233
|
console.log(lavender('✓ Histórico limpo, contexto preservado.\n'));
|
|
185
234
|
continue;
|
|
@@ -187,61 +236,73 @@ export async function startInteractive() {
|
|
|
187
236
|
|
|
188
237
|
if (cmd === '/help') {
|
|
189
238
|
console.log(gray(`
|
|
190
|
-
Comandos
|
|
239
|
+
Comandos de Modo:
|
|
191
240
|
/chat /plan /edit → Mudar modo de operação
|
|
192
|
-
/
|
|
193
|
-
/
|
|
194
|
-
/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
241
|
+
/use [agente] → Usar um Agente Especialista
|
|
242
|
+
/use normal → Voltar para o chat normal
|
|
243
|
+
/swarm → Rodar fluxos complexos
|
|
244
|
+
|
|
245
|
+
Gerenciamento:
|
|
246
|
+
/switch [nome] → Mudar perfil de IA completo
|
|
247
|
+
/model [nome] → Mudar modelo atual
|
|
248
|
+
/config → Perfis e Agentes
|
|
249
|
+
/init → Inicializar .bimmorc.json
|
|
250
|
+
@arquivo → Ler arquivo ou imagem
|
|
198
251
|
`));
|
|
199
252
|
continue;
|
|
200
253
|
}
|
|
201
254
|
|
|
202
|
-
if (cmd === '/
|
|
203
|
-
const bimmoRcPath = path.join(process.cwd(), '.bimmorc.json');
|
|
204
|
-
if (fs.existsSync(bimmoRcPath)) {
|
|
205
|
-
const { overwrite } = await inquirer.prompt([{
|
|
206
|
-
type: 'confirm',
|
|
207
|
-
name: 'overwrite',
|
|
208
|
-
message: 'O arquivo .bimmorc.json já existe. Deseja sobrescrever?',
|
|
209
|
-
default: false
|
|
210
|
-
}]);
|
|
211
|
-
if (!overwrite) continue;
|
|
212
|
-
}
|
|
255
|
+
if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
|
|
213
256
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
257
|
+
if (cmd === '/swarm') {
|
|
258
|
+
const agents = config.agents || {};
|
|
259
|
+
const agentList = Object.keys(agents);
|
|
260
|
+
if (agentList.length < 2) {
|
|
261
|
+
console.log(chalk.yellow('\nCrie pelo menos 2 Agentes em /config primeiro.\n'));
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const { swarmAction } = await inquirer.prompt([{
|
|
265
|
+
type: 'list',
|
|
266
|
+
name: 'swarmAction',
|
|
267
|
+
message: 'Tipo de Enxame:',
|
|
268
|
+
choices: ['Sequencial (A → B)', 'Hierárquico (Líder + Workers)', 'Voltar']
|
|
269
|
+
}]);
|
|
270
|
+
if (swarmAction === 'Voltar') continue;
|
|
271
|
+
const { goal } = await inquirer.prompt([{ type: 'input', name: 'goal', message: 'Objetivo do enxame:' }]);
|
|
228
272
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
273
|
+
try {
|
|
274
|
+
let result;
|
|
275
|
+
if (swarmAction.includes('Sequencial')) {
|
|
276
|
+
const { selectedAgents } = await inquirer.prompt([{ type: 'checkbox', name: 'selectedAgents', message: 'Ordem dos agentes:', choices: agentList }]);
|
|
277
|
+
result = await orchestrator.runSequential(selectedAgents, goal);
|
|
278
|
+
} else {
|
|
279
|
+
const { manager } = await inquirer.prompt([{ type: 'list', name: 'manager', message: 'Líder:', choices: agentList }]);
|
|
280
|
+
const { workers } = await inquirer.prompt([{ type: 'checkbox', name: 'workers', message: 'Workers:', choices: agentList.filter(a => a !== manager) }]);
|
|
281
|
+
result = await orchestrator.runHierarchical(manager, workers, goal);
|
|
282
|
+
}
|
|
283
|
+
console.log(lavender('\n=== RESULTADO FINAL ===\n') + marked(result));
|
|
284
|
+
} catch (e) {
|
|
285
|
+
console.error(chalk.red(`\nErro: ${e.message}`));
|
|
286
|
+
}
|
|
234
287
|
continue;
|
|
235
288
|
}
|
|
236
289
|
|
|
237
|
-
if (cmd === '/config') { await configure(); config = getConfig(); provider = createProvider(config); continue; }
|
|
238
|
-
|
|
239
290
|
if (rawInput === '') continue;
|
|
240
291
|
|
|
241
|
-
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
|
|
294
|
+
// Handler local para SIGINT durante o processamento da IA
|
|
295
|
+
const localInterruptHandler = () => {
|
|
296
|
+
controller.abort();
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Remove temporariamente o handler global de saída
|
|
300
|
+
process.removeAllListeners('SIGINT');
|
|
301
|
+
process.on('SIGINT', localInterruptHandler);
|
|
302
|
+
|
|
242
303
|
let modeInstr = "";
|
|
243
|
-
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN]
|
|
244
|
-
else if (currentMode === 'edit') modeInstr = "\n[MODO EDIT]
|
|
304
|
+
if (currentMode === 'plan') modeInstr = "\n[MODO PLAN] Apenas analise.";
|
|
305
|
+
else if (currentMode === 'edit') modeInstr = "\n[MODO EDIT] Aplique as mudanças agora.";
|
|
245
306
|
|
|
246
307
|
const content = await processInput(rawInput);
|
|
247
308
|
messages.push({
|
|
@@ -250,22 +311,41 @@ Comandos Disponíveis:
|
|
|
250
311
|
});
|
|
251
312
|
|
|
252
313
|
const spinner = ora({
|
|
253
|
-
text: lavender(`bimmo (
|
|
314
|
+
text: lavender(`bimmo pensando... (Ctrl+C para interromper)`),
|
|
254
315
|
color: currentMode === 'edit' ? 'red' : 'magenta'
|
|
255
316
|
}).start();
|
|
256
317
|
|
|
257
318
|
try {
|
|
258
|
-
|
|
319
|
+
let responseText = await provider.sendMessage(messages, { signal: controller.signal });
|
|
259
320
|
spinner.stop();
|
|
321
|
+
const cleanedText = responseText.replace(/<\/?[^>]+(>|$)/g, "");
|
|
260
322
|
messages.push({ role: 'assistant', content: responseText });
|
|
261
|
-
|
|
262
323
|
console.log('\n' + lavender('bimmo') + getModeStyle());
|
|
263
324
|
console.log(lavender('─'.repeat(50)));
|
|
264
|
-
console.log(marked(
|
|
325
|
+
console.log(marked(cleanedText));
|
|
265
326
|
console.log(gray('─'.repeat(50)) + '\n');
|
|
266
327
|
} catch (err) {
|
|
267
328
|
spinner.stop();
|
|
268
|
-
|
|
329
|
+
if (controller.signal.aborted || err.name === 'AbortError') {
|
|
330
|
+
console.log(yellow('\n\n⚠️ Operação interrompida pelo usuário.\n'));
|
|
331
|
+
messages.pop();
|
|
332
|
+
} else {
|
|
333
|
+
console.error(chalk.red('\n✖ Erro:') + ' ' + err.message + '\n');
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
// Restaura o handler global de saída
|
|
337
|
+
process.removeListener('SIGINT', localInterruptHandler);
|
|
338
|
+
process.on('SIGINT', () => {
|
|
339
|
+
exitCounter++;
|
|
340
|
+
if (exitCounter === 1) {
|
|
341
|
+
console.log(gray('\n(Pressione Ctrl+C novamente para sair)'));
|
|
342
|
+
if (exitTimer) clearTimeout(exitTimer);
|
|
343
|
+
exitTimer = setTimeout(() => { exitCounter = 0; }, 2000);
|
|
344
|
+
} else {
|
|
345
|
+
console.log(lavender('\n👋 BIMMO encerrando sessão. Até logo!\n'));
|
|
346
|
+
process.exit(0);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
269
349
|
}
|
|
270
350
|
}
|
|
271
|
-
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createProvider } from './providers/factory.js';
|
|
4
|
+
import { getProjectContext } from './project-context.js';
|
|
5
|
+
|
|
6
|
+
export class SwarmOrchestrator {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.agents = config.agents || {};
|
|
10
|
+
this.profiles = config.profiles || {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Executa uma tarefa sequencialmente através de uma lista de agentes.
|
|
15
|
+
* O output de um agente vira o contexto do próximo.
|
|
16
|
+
*/
|
|
17
|
+
async runSequential(agentNames, goal, options = {}) {
|
|
18
|
+
let currentContext = goal;
|
|
19
|
+
const results = [];
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(`\n🚀 Iniciando Enxame Sequencial: ${agentNames.join(' → ')}\n`));
|
|
22
|
+
|
|
23
|
+
for (const name of agentNames) {
|
|
24
|
+
const agent = this.agents[name];
|
|
25
|
+
if (!agent) throw new Error(`Agente ${name} não encontrado.`);
|
|
26
|
+
|
|
27
|
+
const profile = this.profiles[agent.profile];
|
|
28
|
+
if (!profile) throw new Error(`Perfil ${agent.profile} do agente ${name} não encontrado.`);
|
|
29
|
+
|
|
30
|
+
const agentConfig = {
|
|
31
|
+
...profile,
|
|
32
|
+
model: agent.modelOverride || profile.model
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const provider = createProvider(agentConfig);
|
|
36
|
+
const spinner = ora({
|
|
37
|
+
text: chalk.magenta(`Agente [${name}] trabalhando...`),
|
|
38
|
+
color: agent.mode === 'edit' ? 'red' : 'magenta'
|
|
39
|
+
}).start();
|
|
40
|
+
|
|
41
|
+
const messages = [
|
|
42
|
+
{ role: 'system', content: `Você é o agente ${name}. Sua tarefa específica é: ${agent.role}\n\nMODO ATUAL: ${agent.mode.toUpperCase()}\n${getProjectContext()}` },
|
|
43
|
+
{ role: 'user', content: `CONTEXTO ATUAL:\n${currentContext}\n\nOBJETIVO DO ENXAME:\n${goal}\n\nPor favor, execute sua parte e retorne o resultado final processado.` }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const response = await provider.sendMessage(messages, { signal: options.signal });
|
|
48
|
+
spinner.succeed(chalk.green(`Agente [${name}] concluído.`));
|
|
49
|
+
|
|
50
|
+
currentContext = response;
|
|
51
|
+
results.push({ agent: name, output: response });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
spinner.fail(chalk.red(`Agente [${name}] falhou: ${err.message}`));
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return currentContext;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Executa uma tarefa hierárquica.
|
|
63
|
+
* Um Manager recebe o objetivo, define o que cada Worker deve fazer, e consolida.
|
|
64
|
+
*/
|
|
65
|
+
async runHierarchical(managerName, workerNames, goal, options = {}) {
|
|
66
|
+
console.log(chalk.cyan(`\n👑 Iniciando Enxame Hierárquico (Líder: ${managerName})\n`));
|
|
67
|
+
|
|
68
|
+
// Passo 1: Manager analisa e delega
|
|
69
|
+
const managerOutput = await this.runSequential([managerName], `Analise o objetivo abaixo e descreva o que cada um dos seguintes agentes deve fazer: ${workerNames.join(', ')}.\n\nOBJETIVO: ${goal}`);
|
|
70
|
+
|
|
71
|
+
// Passo 2: Workers executam baseado na delegação do Manager (em paralelo para velocidade)
|
|
72
|
+
console.log(chalk.blue(`\n👷 Workers entrando em ação...\n`));
|
|
73
|
+
const workerPromises = workerNames.map(name =>
|
|
74
|
+
this.runSequential([name], `Baseado no plano do Manager:\n${managerOutput}\n\nExecute sua tarefa para o objetivo: ${goal}`, options)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const workerResults = await Promise.all(workerPromises);
|
|
78
|
+
|
|
79
|
+
// Passo 3: Manager consolida tudo
|
|
80
|
+
const finalResult = await this.runSequential([managerName], `Aqui estão os resultados dos workers:\n${workerResults.join('\n---\n')}\n\nPor favor, consolide tudo em um resultado final perfeito para o objetivo: ${goal}`, options);
|
|
81
|
+
|
|
82
|
+
return finalResult;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -21,7 +21,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
async sendMessage(messages) {
|
|
24
|
+
async sendMessage(messages, options = {}) {
|
|
25
25
|
const systemMessage = messages.find(m => m.role === 'system');
|
|
26
26
|
const userMessages = messages
|
|
27
27
|
.filter(m => m.role !== 'system')
|
|
@@ -30,7 +30,6 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
30
30
|
content: this.formatContent(m.content)
|
|
31
31
|
}));
|
|
32
32
|
|
|
33
|
-
// Converte tools do agent.js para o formato da Anthropic
|
|
34
33
|
const anthropicTools = tools.map(t => ({
|
|
35
34
|
name: t.name,
|
|
36
35
|
description: t.description,
|
|
@@ -44,17 +43,18 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
44
43
|
messages: userMessages,
|
|
45
44
|
tools: anthropicTools,
|
|
46
45
|
temperature: 0.7
|
|
47
|
-
});
|
|
46
|
+
}, { signal: options.signal });
|
|
48
47
|
|
|
49
48
|
if (response.stop_reason === 'tool_use') {
|
|
50
49
|
const toolUse = response.content.find(p => p.type === 'tool_use');
|
|
51
50
|
const tool = tools.find(t => t.name === toolUse.name);
|
|
52
51
|
|
|
53
52
|
if (tool) {
|
|
53
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
54
|
+
|
|
54
55
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
55
56
|
const result = await tool.execute(toolUse.input);
|
|
56
57
|
|
|
57
|
-
// Adiciona a resposta da IA e o resultado da tool ao histórico
|
|
58
58
|
const nextMessages = [
|
|
59
59
|
...messages,
|
|
60
60
|
{ role: 'assistant', content: response.content },
|
|
@@ -68,10 +68,10 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
68
68
|
}
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
-
return this.sendMessage(nextMessages);
|
|
71
|
+
return this.sendMessage(nextMessages, options);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
return response.content[0].text;
|
|
76
76
|
}
|
|
77
|
-
}
|
|
77
|
+
}
|
package/src/providers/base.js
CHANGED
package/src/providers/gemini.js
CHANGED
|
@@ -18,7 +18,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async sendMessage(messages) {
|
|
21
|
+
async sendMessage(messages, options = {}) {
|
|
22
22
|
const systemPrompt = messages.find(m => m.role === 'system')?.content;
|
|
23
23
|
const history = messages
|
|
24
24
|
.filter(m => m.role !== 'system')
|
|
@@ -30,7 +30,6 @@ export class GeminiProvider extends BaseProvider {
|
|
|
30
30
|
|
|
31
31
|
const lastMessageContent = this.formatContent(messages[messages.length - 1].content);
|
|
32
32
|
|
|
33
|
-
// Converte tools do agent.js para o formato do Gemini
|
|
34
33
|
const geminiTools = tools.map(t => ({
|
|
35
34
|
functionDeclarations: [{
|
|
36
35
|
name: t.name,
|
|
@@ -53,19 +52,19 @@ export class GeminiProvider extends BaseProvider {
|
|
|
53
52
|
const result = await chat.sendMessage(lastMessageContent);
|
|
54
53
|
const response = await result.response;
|
|
55
54
|
|
|
56
|
-
// Processamento de Tool Calls no Gemini
|
|
57
55
|
const call = response.candidates[0].content.parts.find(p => p.functionCall);
|
|
58
56
|
if (call) {
|
|
57
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
58
|
+
|
|
59
59
|
const tool = tools.find(t => t.name === call.functionCall.name);
|
|
60
60
|
if (tool) {
|
|
61
61
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
62
|
-
const
|
|
62
|
+
const toolResult = await tool.execute(call.functionCall.args);
|
|
63
63
|
|
|
64
|
-
// No Gemini, enviamos o resultado de volta para o chat
|
|
65
64
|
const resultResponse = await chat.sendMessage([{
|
|
66
65
|
functionResponse: {
|
|
67
66
|
name: call.functionCall.name,
|
|
68
|
-
response: { content:
|
|
67
|
+
response: { content: toolResult }
|
|
69
68
|
}
|
|
70
69
|
}]);
|
|
71
70
|
return resultResponse.response.text();
|
|
@@ -74,4 +73,4 @@ export class GeminiProvider extends BaseProvider {
|
|
|
74
73
|
|
|
75
74
|
return response.text();
|
|
76
75
|
}
|
|
77
|
-
}
|
|
76
|
+
}
|
package/src/providers/grok.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { BaseProvider } from './base.js';
|
|
3
|
+
import { tools } from '../agent.js';
|
|
3
4
|
|
|
4
5
|
export class GrokProvider extends BaseProvider {
|
|
5
6
|
constructor(config) {
|
|
@@ -33,15 +34,15 @@ export class GrokProvider extends BaseProvider {
|
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async sendMessage(messages) {
|
|
37
|
+
async sendMessage(messages, options = {}) {
|
|
37
38
|
const formattedMessages = this.formatMessages(messages);
|
|
38
39
|
|
|
39
40
|
const response = await this.client.chat.completions.create({
|
|
40
41
|
model: this.config.model,
|
|
41
42
|
messages: formattedMessages,
|
|
42
43
|
temperature: 0.7
|
|
43
|
-
});
|
|
44
|
+
}, { signal: options.signal });
|
|
44
45
|
|
|
45
46
|
return response.choices[0].message.content;
|
|
46
47
|
}
|
|
47
|
-
}
|
|
48
|
+
}
|
package/src/providers/ollama.js
CHANGED
|
@@ -13,7 +13,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
13
13
|
|
|
14
14
|
const images = msg.content
|
|
15
15
|
.filter(part => part.type === 'image')
|
|
16
|
-
.map(part => part.data);
|
|
16
|
+
.map(part => part.data);
|
|
17
17
|
|
|
18
18
|
const text = msg.content
|
|
19
19
|
.filter(part => part.type === 'text')
|
|
@@ -28,7 +28,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async sendMessage(messages) {
|
|
31
|
+
async sendMessage(messages, options = {}) {
|
|
32
32
|
const formattedMessages = this.formatMessages(messages);
|
|
33
33
|
|
|
34
34
|
const response = await this.client.chat({
|
|
@@ -38,7 +38,7 @@ export class OllamaProvider extends BaseProvider {
|
|
|
38
38
|
options: {
|
|
39
39
|
temperature: 0.7,
|
|
40
40
|
}
|
|
41
|
-
});
|
|
41
|
+
}, { signal: options.signal });
|
|
42
42
|
|
|
43
43
|
const text = response.message?.content;
|
|
44
44
|
|
|
@@ -48,4 +48,4 @@ export class OllamaProvider extends BaseProvider {
|
|
|
48
48
|
|
|
49
49
|
return text;
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/providers/openai.js
CHANGED
|
@@ -34,7 +34,7 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async sendMessage(messages) {
|
|
37
|
+
async sendMessage(messages, options = {}) {
|
|
38
38
|
const formattedMessages = this.formatMessages(messages);
|
|
39
39
|
|
|
40
40
|
const openAiTools = tools.map(t => ({
|
|
@@ -46,25 +46,26 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
46
46
|
}
|
|
47
47
|
}));
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const requestOptions = {
|
|
50
50
|
model: this.config.model,
|
|
51
51
|
messages: formattedMessages,
|
|
52
52
|
temperature: 0.7
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
// Só envia tools se houver alguma definida para evitar erros em alguns provedores
|
|
56
55
|
if (openAiTools.length > 0) {
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
requestOptions.tools = openAiTools;
|
|
57
|
+
requestOptions.tool_choice = 'auto';
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
const response = await this.client.chat.completions.create(options);
|
|
60
|
+
const response = await this.client.chat.completions.create(requestOptions, { signal: options.signal });
|
|
62
61
|
|
|
63
62
|
const message = response.choices[0].message;
|
|
64
63
|
|
|
65
64
|
if (message.tool_calls) {
|
|
66
65
|
const toolResults = [];
|
|
67
66
|
for (const toolCall of message.tool_calls) {
|
|
67
|
+
if (options.signal?.aborted) throw new Error('Abortado pelo usuário');
|
|
68
|
+
|
|
68
69
|
const tool = tools.find(t => t.name === toolCall.function.name);
|
|
69
70
|
if (tool) {
|
|
70
71
|
console.log(`\n ${tool.name === 'search_internet' ? '🌐' : '🛠️'} Executando: ${tool.name}...`);
|
|
@@ -80,9 +81,9 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const nextMessages = [...formattedMessages, message, ...toolResults];
|
|
83
|
-
return this.sendMessage(nextMessages);
|
|
84
|
+
return this.sendMessage(nextMessages, options);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
return message.content;
|
|
87
88
|
}
|
|
88
|
-
}
|
|
89
|
+
}
|