agent-rev 0.1.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.
- package/dist/api/qwen.d.ts +12 -0
- package/dist/api/qwen.js +150 -0
- package/dist/commands/auth.d.ts +31 -0
- package/dist/commands/auth.js +255 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +42 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +27 -0
- package/dist/commands/repl.d.ts +29 -0
- package/dist/commands/repl.js +1167 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +52 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +353 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +46 -0
- package/dist/core/engine.d.ts +36 -0
- package/dist/core/engine.js +905 -0
- package/dist/core/prompts.d.ts +11 -0
- package/dist/core/prompts.js +126 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +171 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +120 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +58 -0
- package/dist/ui/input.d.ts +24 -0
- package/dist/ui/input.js +244 -0
- package/dist/ui/theme.d.ts +99 -0
- package/dist/ui/theme.js +307 -0
- package/dist/utils/config.d.ts +38 -0
- package/dist/utils/config.js +40 -0
- package/dist/utils/fs.d.ts +7 -0
- package/dist/utils/fs.js +37 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/qwen-auth.d.ts +12 -0
- package/dist/utils/qwen-auth.js +250 -0
- package/dist/utils/sessions.d.ts +17 -0
- package/dist/utils/sessions.js +46 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as fsSync from 'fs';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { CLI_REGISTRY } from '../types.js';
|
|
9
|
+
import { writeJson, ensureDir, readJson, listDir, fileExists } from '../utils/fs.js';
|
|
10
|
+
import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig } from '../utils/config.js';
|
|
11
|
+
import { log } from '../utils/logger.js';
|
|
12
|
+
import { AgentEngine } from '../core/engine.js';
|
|
13
|
+
import { qwenAuthStatus, QWEN_AGENT_HOME } from '../utils/qwen-auth.js';
|
|
14
|
+
import { renderWelcomePanel, renderHelpHint, renderSectionBox, renderMultiSectionBox } from '../ui/theme.js';
|
|
15
|
+
import { FixedInput } from '../ui/input.js';
|
|
16
|
+
import { newSession, saveSession } from '../utils/sessions.js';
|
|
17
|
+
// Package info
|
|
18
|
+
function getPkgInfo() {
|
|
19
|
+
const __filename = new URL(import.meta.url).pathname;
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
const p = path.join(__dirname, '..', '..', 'package.json');
|
|
22
|
+
try {
|
|
23
|
+
const c = JSON.parse(fsSync.readFileSync(p, 'utf-8'));
|
|
24
|
+
return { name: c.name, version: c.version };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { name: 'agent-mp', version: '0.1.0' };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const pkgInfo = getPkgInfo();
|
|
31
|
+
const CLI_AUTH_COMMANDS = {
|
|
32
|
+
opencode: 'opencode providers login',
|
|
33
|
+
qwen: 'qwen auth qwen-oauth',
|
|
34
|
+
gemini: 'gemini auth login',
|
|
35
|
+
claude: 'claude auth login',
|
|
36
|
+
codex: 'codex auth login',
|
|
37
|
+
cn: 'cn auth login',
|
|
38
|
+
};
|
|
39
|
+
const CLI_AUTH_STATUS = {
|
|
40
|
+
qwen: 'qwen auth status',
|
|
41
|
+
claude: 'claude auth status',
|
|
42
|
+
gemini: 'gemini auth status',
|
|
43
|
+
codex: 'codex auth status',
|
|
44
|
+
};
|
|
45
|
+
// Check if CLI is installed
|
|
46
|
+
function isCliInstalled(cliName) {
|
|
47
|
+
try {
|
|
48
|
+
execSync(`which ${cliName}`, { stdio: 'ignore' });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function checkCliAuth(cliName) {
|
|
56
|
+
// For qwen: use qwenAuthStatus which attempts token refresh + fetches email
|
|
57
|
+
if (cliName === 'qwen') {
|
|
58
|
+
const { authenticated, email } = await qwenAuthStatus();
|
|
59
|
+
return { ok: authenticated, email };
|
|
60
|
+
}
|
|
61
|
+
// For other CLIs, use their status command
|
|
62
|
+
const statusCmd = CLI_AUTH_STATUS[cliName];
|
|
63
|
+
if (!statusCmd)
|
|
64
|
+
return { ok: true };
|
|
65
|
+
try {
|
|
66
|
+
const out = execSync(`${statusCmd} 2>&1`, { encoding: 'utf-8' });
|
|
67
|
+
const ok = out.includes('Authentication Method') ||
|
|
68
|
+
out.includes('Logged in') ||
|
|
69
|
+
out.includes('authenticated') ||
|
|
70
|
+
out.includes('✓') ||
|
|
71
|
+
out.includes('session active');
|
|
72
|
+
return { ok };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { ok: false };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function ask(rl, q) {
|
|
79
|
+
return new Promise((resolve) => rl.question(q, resolve));
|
|
80
|
+
}
|
|
81
|
+
function arrowSelect(title, options) {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const maxVisible = 12;
|
|
84
|
+
const total = options.length;
|
|
85
|
+
const visible = Math.min(maxVisible, total);
|
|
86
|
+
let selected = 0;
|
|
87
|
+
let offset = 0;
|
|
88
|
+
let linesDrawn = 0;
|
|
89
|
+
if (!process.stdin.isTTY) {
|
|
90
|
+
resolve(options[0]);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const clearDrawn = () => {
|
|
94
|
+
if (linesDrawn > 0) {
|
|
95
|
+
// Move up linesDrawn lines, clearing each
|
|
96
|
+
for (let i = 0; i < linesDrawn; i++) {
|
|
97
|
+
process.stdout.write('\x1b[1A'); // up one line
|
|
98
|
+
process.stdout.write('\x1b[2K'); // clear entire line
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const draw = () => {
|
|
103
|
+
clearDrawn();
|
|
104
|
+
// Scroll offset
|
|
105
|
+
if (total > maxVisible) {
|
|
106
|
+
if (selected < offset)
|
|
107
|
+
offset = selected;
|
|
108
|
+
else if (selected >= offset + maxVisible)
|
|
109
|
+
offset = selected - maxVisible + 1;
|
|
110
|
+
}
|
|
111
|
+
const lines = [];
|
|
112
|
+
lines.push(chalk.bold(` ${title}`));
|
|
113
|
+
lines.push(chalk.dim(' ─────────────────────────────────'));
|
|
114
|
+
const end = Math.min(offset + maxVisible, total);
|
|
115
|
+
for (let i = offset; i < end; i++) {
|
|
116
|
+
if (i === selected) {
|
|
117
|
+
lines.push(chalk.cyan(` ❯ ${options[i]}`));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
lines.push(` ${options[i]}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
124
|
+
linesDrawn = lines.length;
|
|
125
|
+
};
|
|
126
|
+
// Take raw control of stdin
|
|
127
|
+
process.stdin.setRawMode(true);
|
|
128
|
+
process.stdin.resume();
|
|
129
|
+
process.stdout.write('\x1b[?25l'); // hide cursor
|
|
130
|
+
draw();
|
|
131
|
+
const onData = (buf) => {
|
|
132
|
+
const key = buf.toString();
|
|
133
|
+
if (key === '\x1b[A' && selected > 0) {
|
|
134
|
+
selected--;
|
|
135
|
+
draw();
|
|
136
|
+
}
|
|
137
|
+
else if (key === '\x1b[B' && selected < total - 1) {
|
|
138
|
+
selected++;
|
|
139
|
+
draw();
|
|
140
|
+
}
|
|
141
|
+
else if (key === '\r' || key === '\n') {
|
|
142
|
+
finish(options[selected]);
|
|
143
|
+
}
|
|
144
|
+
else if (key === '\x03') {
|
|
145
|
+
finish(options[0]);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const finish = (value) => {
|
|
149
|
+
process.stdin.removeListener('data', onData);
|
|
150
|
+
process.stdin.setRawMode(false);
|
|
151
|
+
process.stdin.pause();
|
|
152
|
+
process.stdout.write('\x1b[?25h'); // show cursor
|
|
153
|
+
// Replace menu with final selection
|
|
154
|
+
clearDrawn();
|
|
155
|
+
console.log(chalk.dim(` ${title}`) + ' ' + chalk.green(value));
|
|
156
|
+
// Force flush and reset
|
|
157
|
+
process.stdout.write('');
|
|
158
|
+
resolve(value);
|
|
159
|
+
};
|
|
160
|
+
process.stdin.on('data', onData);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async function selectOption(rl, title, options) {
|
|
164
|
+
// Pause readline so arrowSelect can take raw control of stdin
|
|
165
|
+
rl.pause();
|
|
166
|
+
const result = await arrowSelect(title, options);
|
|
167
|
+
// Ensure raw mode is completely off
|
|
168
|
+
if (process.stdin.isTTY) {
|
|
169
|
+
process.stdin.setRawMode(false);
|
|
170
|
+
}
|
|
171
|
+
process.stdin.pause();
|
|
172
|
+
// Reset terminal using stty to ensure clean state
|
|
173
|
+
try {
|
|
174
|
+
execSync('stty sane', { stdio: 'ignore' });
|
|
175
|
+
}
|
|
176
|
+
catch { }
|
|
177
|
+
rl.resume();
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
function detectInstalledClis() {
|
|
181
|
+
const installed = [];
|
|
182
|
+
for (const [name, info] of Object.entries(CLI_REGISTRY)) {
|
|
183
|
+
try {
|
|
184
|
+
const p = execSync(`which ${info.command}`, { encoding: 'utf-8' }).trim();
|
|
185
|
+
installed.push({ name, info, path: p });
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
}
|
|
189
|
+
return installed;
|
|
190
|
+
}
|
|
191
|
+
function detectModels(cliName) {
|
|
192
|
+
try {
|
|
193
|
+
switch (cliName) {
|
|
194
|
+
case 'claude': {
|
|
195
|
+
const help = execSync('claude --help 2>/dev/null', { encoding: 'utf-8' });
|
|
196
|
+
const models = help.match(/'([^']+)'/g)?.map((m) => m.replace(/'/g, '')).filter(Boolean);
|
|
197
|
+
if (models?.length)
|
|
198
|
+
return [...new Set(models)];
|
|
199
|
+
return ['opus', 'sonnet', 'haiku'];
|
|
200
|
+
}
|
|
201
|
+
case 'qwen': return ['coder-model'];
|
|
202
|
+
case 'gemini': {
|
|
203
|
+
const help = execSync('gemini --help 2>/dev/null', { encoding: 'utf-8' });
|
|
204
|
+
const models = help.match(/'([^']+)'/g)?.map((m) => m.replace(/'/g, '')).filter(Boolean);
|
|
205
|
+
if (models?.length)
|
|
206
|
+
return [...new Set(models)];
|
|
207
|
+
return ['gemini-2.5-pro', 'gemini-2.5-flash'];
|
|
208
|
+
}
|
|
209
|
+
case 'codex': {
|
|
210
|
+
const help = execSync('codex --help 2>/dev/null', { encoding: 'utf-8' });
|
|
211
|
+
const models = help.match(/-m\s+(\S+)/g)?.map((m) => m.split(/\s+/)[1]);
|
|
212
|
+
if (models?.length)
|
|
213
|
+
return [...new Set(models)];
|
|
214
|
+
return ['gpt-4o'];
|
|
215
|
+
}
|
|
216
|
+
case 'cn': return ['default (hub)'];
|
|
217
|
+
case 'opencode': {
|
|
218
|
+
const out = execSync('opencode models 2>/dev/null', { encoding: 'utf-8' });
|
|
219
|
+
return out.trim().split('\n').filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
case 'aider': return ['default'];
|
|
222
|
+
default: return ['default'];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return ['default'];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function buildCmd(cliName, model) {
|
|
230
|
+
const info = CLI_REGISTRY[cliName];
|
|
231
|
+
if (!info)
|
|
232
|
+
return `${cliName} -m ${model}`;
|
|
233
|
+
if (cliName === 'codex')
|
|
234
|
+
return `codex exec -m ${model}`;
|
|
235
|
+
if (cliName === 'cn' && model === 'default (hub)')
|
|
236
|
+
return 'cn --auto -p';
|
|
237
|
+
if (cliName === 'opencode')
|
|
238
|
+
return `opencode run -m ${model}`;
|
|
239
|
+
const extra = info.extraFlags ? ` ${info.extraFlags}` : '';
|
|
240
|
+
const promptFlag = info.promptFlag ? ` ${info.promptFlag}` : '';
|
|
241
|
+
return `${info.command} ${info.modelFlag} ${model}${extra}${promptFlag}`.trim();
|
|
242
|
+
}
|
|
243
|
+
async function cmdLogin(rl) {
|
|
244
|
+
console.log(chalk.bold.cyan('\n Login\n'));
|
|
245
|
+
// If active provider is Qwen, do fresh OAuth for corporate account
|
|
246
|
+
const auth = await loadAuth();
|
|
247
|
+
if (auth.activeProvider === 'qwen') {
|
|
248
|
+
console.log(chalk.dim(' Qwen login for corporate account\n'));
|
|
249
|
+
console.log(chalk.dim(' Corporate credentials stored in: ~/.agent-cli/\n'));
|
|
250
|
+
console.log(chalk.yellow(' IMPORTANT: Use your CORPORATE account when prompted.\n'));
|
|
251
|
+
console.log(chalk.yellow(' The browser will open. If you see your personal account,'));
|
|
252
|
+
console.log(chalk.yellow(' log out and sign in with your corporate account.\n'));
|
|
253
|
+
try {
|
|
254
|
+
const { execSync } = await import('child_process');
|
|
255
|
+
const personalCreds = path.join(os.homedir(), '.qwen', 'oauth_creds.json');
|
|
256
|
+
const corporateCreds = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
|
|
257
|
+
// Backup personal credentials
|
|
258
|
+
let personalBackup = null;
|
|
259
|
+
if (await fileExists(personalCreds)) {
|
|
260
|
+
personalBackup = await fs.readFile(personalCreds, 'utf-8');
|
|
261
|
+
await fs.unlink(personalCreds);
|
|
262
|
+
}
|
|
263
|
+
// Remove corporate creds to force fresh login
|
|
264
|
+
await fs.unlink(corporateCreds).catch(() => { });
|
|
265
|
+
// Login with Qwen CLI - forces fresh login since personal creds are backed up
|
|
266
|
+
execSync('qwen auth qwen-oauth', { stdio: 'inherit' });
|
|
267
|
+
// Copy NEW credentials to corporate directory
|
|
268
|
+
if (await fileExists(personalCreds)) {
|
|
269
|
+
const newCreds = await fs.readFile(personalCreds, 'utf-8');
|
|
270
|
+
await fs.writeFile(corporateCreds, newCreds);
|
|
271
|
+
}
|
|
272
|
+
// Restore personal credentials
|
|
273
|
+
if (personalBackup) {
|
|
274
|
+
await fs.writeFile(personalCreds, personalBackup);
|
|
275
|
+
}
|
|
276
|
+
const emailInput = await ask(rl, ' Your email (optional, for display): ');
|
|
277
|
+
auth.entries = auth.entries.filter((e) => e.provider !== 'qwen');
|
|
278
|
+
auth.entries.push({ provider: 'qwen', method: 'oauth', ...(emailInput.trim() ? { email: emailInput.trim() } : {}) });
|
|
279
|
+
await saveAuth(auth);
|
|
280
|
+
console.log(chalk.green(' Corporate auth recorded'));
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
console.log(chalk.red(' Login failed: ' + err.message));
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// For other providers, use their CLI
|
|
289
|
+
console.log(chalk.dim(' Available providers:'));
|
|
290
|
+
for (const [id, cmd] of Object.entries(CLI_AUTH_COMMANDS)) {
|
|
291
|
+
const installed = isCliInstalled(id) ? chalk.green('✓') : chalk.red('✗');
|
|
292
|
+
console.log(chalk.dim(` ${installed} ${id} - uses: ${cmd}`));
|
|
293
|
+
}
|
|
294
|
+
const provider = await ask(rl, '\n Provider: ');
|
|
295
|
+
const authCmd = CLI_AUTH_COMMANDS[provider.trim()];
|
|
296
|
+
if (!authCmd) {
|
|
297
|
+
console.log(chalk.red(` No auth command for: ${provider}`));
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
console.log(chalk.blue(` Running: ${authCmd}`));
|
|
301
|
+
console.log(chalk.dim(' (This will open your browser for login)\n'));
|
|
302
|
+
try {
|
|
303
|
+
execSync(authCmd, { stdio: 'inherit' });
|
|
304
|
+
const auth = await loadAuth();
|
|
305
|
+
auth.entries = auth.entries.filter((e) => e.provider !== provider.trim());
|
|
306
|
+
auth.entries.push({ provider: provider.trim(), method: 'oauth' });
|
|
307
|
+
auth.activeProvider = provider.trim();
|
|
308
|
+
await saveAuth(auth);
|
|
309
|
+
console.log(chalk.green(` Auth recorded for ${provider}`));
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
console.log(chalk.red(' Login failed.'));
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function cmdSetup(rl) {
|
|
318
|
+
console.log(chalk.bold.cyan('\n Setup Wizard\n'));
|
|
319
|
+
const installed = detectInstalledClis();
|
|
320
|
+
if (installed.length === 0) {
|
|
321
|
+
console.log(chalk.red(' No CLIs detected. Install at least one.'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
console.log(chalk.bold(' Detected CLIs:'));
|
|
325
|
+
for (const c of installed)
|
|
326
|
+
log.cliList(c.name, c.path);
|
|
327
|
+
console.log(chalk.bold('\n Multi-CLI Configuration'));
|
|
328
|
+
const cliConfig = await loadCliConfig();
|
|
329
|
+
const roles = ['orchestrator', 'implementor', 'reviewer'];
|
|
330
|
+
for (const role of roles) {
|
|
331
|
+
console.log(chalk.yellow(`\n ${role.toUpperCase()}`));
|
|
332
|
+
const cliName = await selectOption(rl, `CLI for ${role}:`, installed.map((c) => c.name));
|
|
333
|
+
if (!CLI_REGISTRY[cliName]) {
|
|
334
|
+
console.log(chalk.red(` Unknown CLI: ${cliName}`));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const models = detectModels(cliName);
|
|
338
|
+
const model = await selectOption(rl, `Model for ${cliName}:`, models);
|
|
339
|
+
cliConfig.roles[role] = { cli: cliName, model };
|
|
340
|
+
// Ask about fallback for this role
|
|
341
|
+
const fbAnswer = await ask(rl, ` Configure fallback for ${role}? (y/N): `);
|
|
342
|
+
if (fbAnswer.toLowerCase() === 'y') {
|
|
343
|
+
const otherClis = installed.filter((c) => c.name !== cliName);
|
|
344
|
+
if (otherClis.length === 0) {
|
|
345
|
+
console.log(chalk.dim(' No other CLIs available for fallback.'));
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const fbCliName = await selectOption(rl, `Fallback CLI for ${role}:`, otherClis.map((c) => c.name));
|
|
349
|
+
const fbModels = detectModels(fbCliName);
|
|
350
|
+
const fbModel = await selectOption(rl, `Fallback model for ${fbCliName}:`, fbModels);
|
|
351
|
+
cliConfig.roles[role].fallbackCli = fbCliName;
|
|
352
|
+
cliConfig.roles[role].fallbackModel = fbModel;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const delib = await ask(rl, '\n Enable deliberation? (y/N): ');
|
|
357
|
+
if (delib.toLowerCase() === 'y') {
|
|
358
|
+
cliConfig.deliberation.enabled = true;
|
|
359
|
+
const rounds = await ask(rl, ' Max rounds [4]: ');
|
|
360
|
+
cliConfig.deliberation.max_rounds = parseInt(rounds) || 4;
|
|
361
|
+
console.log(chalk.yellow('\n PROPOSER'));
|
|
362
|
+
cliConfig.roles.proposer = { cli: (await ask(rl, ' CLI: ')).trim(), model: (await ask(rl, ' Model: ')).trim() };
|
|
363
|
+
console.log(chalk.yellow('\n CRITIC'));
|
|
364
|
+
cliConfig.roles.critic = { cli: (await ask(rl, ' CLI: ')).trim(), model: (await ask(rl, ' Model: ')).trim() };
|
|
365
|
+
}
|
|
366
|
+
// Global fallback
|
|
367
|
+
console.log(chalk.yellow('\n FALLBACK GLOBAL'));
|
|
368
|
+
console.log(chalk.dim(' Si un rol Y su fallback individual fallan, se usa este CLI como ultimo recurso.'));
|
|
369
|
+
const globalFbAnswer = await ask(rl, ' Configurar fallback global? (y/N): ');
|
|
370
|
+
if (globalFbAnswer.toLowerCase() === 'y') {
|
|
371
|
+
const fbCliName = await selectOption(rl, 'Fallback global CLI:', installed.map((c) => c.name));
|
|
372
|
+
const fbModels = detectModels(fbCliName);
|
|
373
|
+
const fbModel = await selectOption(rl, `Fallback global model for ${fbCliName}:`, fbModels);
|
|
374
|
+
cliConfig.fallback_global = { cli: fbCliName, model: fbModel };
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
delete cliConfig.fallback_global;
|
|
378
|
+
}
|
|
379
|
+
await saveCliConfig(cliConfig);
|
|
380
|
+
const projectDir = process.cwd();
|
|
381
|
+
const projectName = path.basename(projectDir);
|
|
382
|
+
// Helper to build a CliRole with optional fallback
|
|
383
|
+
const buildRoleWithFallback = (roleKey) => {
|
|
384
|
+
const r = cliConfig.roles[roleKey];
|
|
385
|
+
const cli = r?.cli || '';
|
|
386
|
+
const model = r?.model || '';
|
|
387
|
+
const base = { cli, model, cmd: buildCmd(cli, model) };
|
|
388
|
+
if (r?.fallbackCli && r?.fallbackModel) {
|
|
389
|
+
return { ...base, fallback: { cli: r.fallbackCli, model: r.fallbackModel, cmd: buildCmd(r.fallbackCli, r.fallbackModel) } };
|
|
390
|
+
}
|
|
391
|
+
return base;
|
|
392
|
+
};
|
|
393
|
+
const agentConfig = {
|
|
394
|
+
project: projectName,
|
|
395
|
+
description: '',
|
|
396
|
+
stack: 'generic',
|
|
397
|
+
roles: {
|
|
398
|
+
orchestrator: buildRoleWithFallback('orchestrator'),
|
|
399
|
+
implementor: buildRoleWithFallback('implementor'),
|
|
400
|
+
reviewer: buildRoleWithFallback('reviewer'),
|
|
401
|
+
},
|
|
402
|
+
deliberation: cliConfig.deliberation.enabled ? {
|
|
403
|
+
proposer: { cli: cliConfig.roles.proposer?.cli || '', model: cliConfig.roles.proposer?.model || '', cmd: buildCmd(cliConfig.roles.proposer?.cli || '', cliConfig.roles.proposer?.model || '') },
|
|
404
|
+
critic: { cli: cliConfig.roles.critic?.cli || '', model: cliConfig.roles.critic?.model || '', cmd: buildCmd(cliConfig.roles.critic?.cli || '', cliConfig.roles.critic?.model || '') },
|
|
405
|
+
max_rounds: cliConfig.deliberation.max_rounds,
|
|
406
|
+
stop_on: 'ACCEPT',
|
|
407
|
+
output_dir: '.agent/tasks/{ID}/debate/',
|
|
408
|
+
} : undefined,
|
|
409
|
+
fallback_global: cliConfig.fallback_global ? { cli: cliConfig.fallback_global.cli, model: cliConfig.fallback_global.model, cmd: buildCmd(cliConfig.fallback_global.cli, cliConfig.fallback_global.model) } : undefined,
|
|
410
|
+
structure: { approved_dirs: ['.'], forbidden_dirs: ['node_modules', 'dist', '.git'] },
|
|
411
|
+
};
|
|
412
|
+
await ensureDir(path.join(projectDir, '.agent'));
|
|
413
|
+
await writeJson(path.join(projectDir, '.agent', 'config.json'), agentConfig);
|
|
414
|
+
console.log(chalk.green(' .agent/config.json generated'));
|
|
415
|
+
const templates = {
|
|
416
|
+
'.agent/INDEX.md': `# ${projectName} - Agent Entry Point\n\n## Navegacion por rol\n| Si sos... | Lee primero... |\n|-----------|----------------|\n| ORCHESTRATOR | .agent/rules/workflow.md |\n| IMPLEMENTOR | .agent/tasks/{ID}/plan.json |\n| REVIEWER | .agent/rules/workflow.md |\n`,
|
|
417
|
+
'.agent/rules/workflow.md': '# Agent Workflow Contract\n\n## REGLA #1 - Separacion de roles\nUn rol NUNCA ejecuta acciones de otro rol.\n\n## Roles\n- ORCHESTRATOR: planifica\n- IMPLEMENTOR: ejecuta\n- REVIEWER: valida\n',
|
|
418
|
+
'.agent/rules/structure.md': '# Estructura\n\n## Aprobados\n.\n\n## Prohibidos\nnode_modules, dist, .git\n',
|
|
419
|
+
'.agent/rules/patterns.md': '# Patrones\n\n- kebab-case: archivos\n- PascalCase: clases\n- camelCase: variables\n',
|
|
420
|
+
'.agent/context/architecture.md': '# Arquitectura - NIVEL 0\n\n| Servicio | Stack | Puerto | Detalle |\n|----------|-------|--------|---------|\n| - | - | - | - |\n',
|
|
421
|
+
'.agent/AGENT.md': '# Sub-Agentes\n\n1. Lee .agent/INDEX.md\n2. Lee .agent/rules/workflow.md\n3. Actua SOLO dentro de tu rol\n',
|
|
422
|
+
};
|
|
423
|
+
for (const [fp, content] of Object.entries(templates)) {
|
|
424
|
+
await fs.mkdir(path.join(projectDir, path.dirname(fp)), { recursive: true });
|
|
425
|
+
await fs.writeFile(path.join(projectDir, fp), content, 'utf-8');
|
|
426
|
+
}
|
|
427
|
+
console.log(chalk.green(' Templates generated'));
|
|
428
|
+
console.log(chalk.green('\n Setup complete!'));
|
|
429
|
+
}
|
|
430
|
+
async function cmdConfigMulti(rl) {
|
|
431
|
+
console.log(chalk.bold.cyan('\n Multi-CLI Configuration\n'));
|
|
432
|
+
const installed = detectInstalledClis();
|
|
433
|
+
if (installed.length === 0) {
|
|
434
|
+
console.log(chalk.red(' No CLIs detected.'));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.log(chalk.bold(' Detected CLIs:'));
|
|
438
|
+
for (const c of installed)
|
|
439
|
+
log.cliList(c.name, c.path);
|
|
440
|
+
const cliConfig = await loadCliConfig();
|
|
441
|
+
const roles = ['orchestrator', 'implementor', 'reviewer'];
|
|
442
|
+
for (const role of roles) {
|
|
443
|
+
const current = cliConfig.roles[role];
|
|
444
|
+
const fbInfo = current?.fallbackCli ? ` | fb: ${current.fallbackCli}/${current.fallbackModel}` : '';
|
|
445
|
+
console.log(chalk.yellow(`\n ${role.toUpperCase()}${current ? ` [current: ${current.cli}/${current.model}${fbInfo}]` : ''}`));
|
|
446
|
+
const cliName = await selectOption(rl, `CLI for ${role}:`, installed.map((c) => c.name));
|
|
447
|
+
const models = detectModels(cliName);
|
|
448
|
+
const model = await selectOption(rl, `Model for ${cliName}:`, models);
|
|
449
|
+
cliConfig.roles[role] = { cli: cliName, model };
|
|
450
|
+
// Ask about fallback for this role
|
|
451
|
+
const fbAnswer = await ask(rl, ` Configure fallback for ${role}? (y/N): `);
|
|
452
|
+
if (fbAnswer.toLowerCase() === 'y') {
|
|
453
|
+
const otherClis = installed.filter((c) => c.name !== cliName);
|
|
454
|
+
if (otherClis.length === 0) {
|
|
455
|
+
console.log(chalk.dim(' No other CLIs available for fallback.'));
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
const fbCliName = await selectOption(rl, `Fallback CLI for ${role}:`, otherClis.map((c) => c.name));
|
|
459
|
+
const fbModels = detectModels(fbCliName);
|
|
460
|
+
const fbModel = await selectOption(rl, `Fallback model for ${fbCliName}:`, fbModels);
|
|
461
|
+
cliConfig.roles[role].fallbackCli = fbCliName;
|
|
462
|
+
cliConfig.roles[role].fallbackModel = fbModel;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
delete cliConfig.roles[role].fallbackCli;
|
|
467
|
+
delete cliConfig.roles[role].fallbackModel;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Global fallback
|
|
471
|
+
const currentGlobalFb = cliConfig.fallback_global;
|
|
472
|
+
console.log(chalk.yellow(`\n FALLBACK GLOBAL${currentGlobalFb ? ` [current: ${currentGlobalFb.cli}/${currentGlobalFb.model}]` : ''}`));
|
|
473
|
+
console.log(chalk.dim(' Si un rol Y su fallback individual fallan, se usa este CLI como ultimo recurso.'));
|
|
474
|
+
const globalFbAnswer = await ask(rl, ' Configurar fallback global? (y/N): ');
|
|
475
|
+
if (globalFbAnswer.toLowerCase() === 'y') {
|
|
476
|
+
const fbCliName = await selectOption(rl, 'Fallback global CLI:', installed.map((c) => c.name));
|
|
477
|
+
const fbModels = detectModels(fbCliName);
|
|
478
|
+
const fbModel = await selectOption(rl, `Fallback global model for ${fbCliName}:`, fbModels);
|
|
479
|
+
cliConfig.fallback_global = { cli: fbCliName, model: fbModel };
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
delete cliConfig.fallback_global;
|
|
483
|
+
}
|
|
484
|
+
await saveCliConfig(cliConfig);
|
|
485
|
+
const projectDir = process.cwd();
|
|
486
|
+
const projectName = path.basename(projectDir);
|
|
487
|
+
// Helper to build a CliRole with optional fallback
|
|
488
|
+
const buildRoleWithFallback = (roleKey) => {
|
|
489
|
+
const r = cliConfig.roles[roleKey];
|
|
490
|
+
const cli = r?.cli || '';
|
|
491
|
+
const model = r?.model || '';
|
|
492
|
+
const base = { cli, model, cmd: buildCmd(cli, model) };
|
|
493
|
+
if (r?.fallbackCli && r?.fallbackModel) {
|
|
494
|
+
return { ...base, fallback: { cli: r.fallbackCli, model: r.fallbackModel, cmd: buildCmd(r.fallbackCli, r.fallbackModel) } };
|
|
495
|
+
}
|
|
496
|
+
return base;
|
|
497
|
+
};
|
|
498
|
+
const agentConfig = {
|
|
499
|
+
project: projectName,
|
|
500
|
+
description: '',
|
|
501
|
+
stack: 'generic',
|
|
502
|
+
roles: {
|
|
503
|
+
orchestrator: buildRoleWithFallback('orchestrator'),
|
|
504
|
+
implementor: buildRoleWithFallback('implementor'),
|
|
505
|
+
reviewer: buildRoleWithFallback('reviewer'),
|
|
506
|
+
},
|
|
507
|
+
deliberation: cliConfig.deliberation?.enabled ? {
|
|
508
|
+
proposer: { cli: cliConfig.roles.proposer?.cli || '', model: cliConfig.roles.proposer?.model || '', cmd: buildCmd(cliConfig.roles.proposer?.cli || '', cliConfig.roles.proposer?.model || '') },
|
|
509
|
+
critic: { cli: cliConfig.roles.critic?.cli || '', model: cliConfig.roles.critic?.model || '', cmd: buildCmd(cliConfig.roles.critic?.cli || '', cliConfig.roles.critic?.model || '') },
|
|
510
|
+
max_rounds: cliConfig.deliberation.max_rounds,
|
|
511
|
+
stop_on: 'ACCEPT',
|
|
512
|
+
output_dir: '.agent/tasks/{ID}/debate/',
|
|
513
|
+
} : undefined,
|
|
514
|
+
fallback_global: cliConfig.fallback_global ? { cli: cliConfig.fallback_global.cli, model: cliConfig.fallback_global.model, cmd: buildCmd(cliConfig.fallback_global.cli, cliConfig.fallback_global.model) } : undefined,
|
|
515
|
+
structure: { approved_dirs: ['.'], forbidden_dirs: ['node_modules', 'dist', '.git'] },
|
|
516
|
+
};
|
|
517
|
+
await ensureDir(path.join(projectDir, '.agent'));
|
|
518
|
+
await writeJson(path.join(projectDir, '.agent', 'config.json'), agentConfig);
|
|
519
|
+
console.log(chalk.green('\n Config updated'));
|
|
520
|
+
}
|
|
521
|
+
async function cmdConfigOneRole(rl, role) {
|
|
522
|
+
const installed = detectInstalledClis();
|
|
523
|
+
if (installed.length === 0) {
|
|
524
|
+
console.log(chalk.red(' No CLIs detected.'));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const cliConfig = await loadCliConfig();
|
|
528
|
+
const current = cliConfig.roles[role];
|
|
529
|
+
console.log(chalk.yellow(`\n ${role.toUpperCase()}${current ? ` [current: ${current.cli}/${current.model}]` : ''}`));
|
|
530
|
+
const cliName = await selectOption(rl, `CLI for ${role}:`, installed.map((c) => c.name));
|
|
531
|
+
const models = detectModels(cliName);
|
|
532
|
+
const model = await selectOption(rl, `Model for ${cliName}:`, models);
|
|
533
|
+
cliConfig.roles[role] = { cli: cliName, model };
|
|
534
|
+
await saveCliConfig(cliConfig);
|
|
535
|
+
// Regenerate .agent/config.json if it exists
|
|
536
|
+
const projectDir = process.cwd();
|
|
537
|
+
const projectName = path.basename(projectDir);
|
|
538
|
+
const r = cliConfig.roles;
|
|
539
|
+
const buildRoleWithFallback = (roleKey) => {
|
|
540
|
+
const rr = r[roleKey];
|
|
541
|
+
const cli = rr?.cli || '';
|
|
542
|
+
const mod = rr?.model || '';
|
|
543
|
+
const base = { cli, model: mod, cmd: buildCmd(cli, mod) };
|
|
544
|
+
if (rr?.fallbackCli && rr?.fallbackModel) {
|
|
545
|
+
return { ...base, fallback: { cli: rr.fallbackCli, model: rr.fallbackModel, cmd: buildCmd(rr.fallbackCli, rr.fallbackModel) } };
|
|
546
|
+
}
|
|
547
|
+
return base;
|
|
548
|
+
};
|
|
549
|
+
const agentConfig = {
|
|
550
|
+
project: projectName,
|
|
551
|
+
description: '',
|
|
552
|
+
stack: 'generic',
|
|
553
|
+
roles: {
|
|
554
|
+
orchestrator: buildRoleWithFallback('orchestrator'),
|
|
555
|
+
implementor: buildRoleWithFallback('implementor'),
|
|
556
|
+
reviewer: buildRoleWithFallback('reviewer'),
|
|
557
|
+
...(r.explorer ? { explorer: { cli: r.explorer.cli, model: r.explorer.model, cmd: buildCmd(r.explorer.cli, r.explorer.model) } } : {}),
|
|
558
|
+
},
|
|
559
|
+
deliberation: cliConfig.deliberation?.enabled ? {
|
|
560
|
+
proposer: { cli: r.proposer?.cli || '', model: r.proposer?.model || '', cmd: buildCmd(r.proposer?.cli || '', r.proposer?.model || '') },
|
|
561
|
+
critic: { cli: r.critic?.cli || '', model: r.critic?.model || '', cmd: buildCmd(r.critic?.cli || '', r.critic?.model || '') },
|
|
562
|
+
max_rounds: cliConfig.deliberation.max_rounds,
|
|
563
|
+
stop_on: 'ACCEPT',
|
|
564
|
+
output_dir: '.agent/tasks/{ID}/debate/',
|
|
565
|
+
} : undefined,
|
|
566
|
+
fallback_global: cliConfig.fallback_global ? { cli: cliConfig.fallback_global.cli, model: cliConfig.fallback_global.model, cmd: buildCmd(cliConfig.fallback_global.cli, cliConfig.fallback_global.model) } : undefined,
|
|
567
|
+
structure: { approved_dirs: ['.'], forbidden_dirs: ['node_modules', 'dist', '.git'] },
|
|
568
|
+
};
|
|
569
|
+
await ensureDir(path.join(projectDir, '.agent'));
|
|
570
|
+
await writeJson(path.join(projectDir, '.agent', 'config.json'), agentConfig);
|
|
571
|
+
console.log(chalk.green(`\n ✓ ${role} updated → ${cliName}/${model}`));
|
|
572
|
+
}
|
|
573
|
+
async function cmdStatus(fi) {
|
|
574
|
+
const cliConfig = await loadCliConfig();
|
|
575
|
+
const auth = await loadAuth();
|
|
576
|
+
const sections = [];
|
|
577
|
+
sections.push({
|
|
578
|
+
title: 'Auth',
|
|
579
|
+
rows: [
|
|
580
|
+
{ key: 'Providers', value: auth.entries.map((e) => `${e.provider} (${e.method})`).join(', ') || 'none' },
|
|
581
|
+
...(auth.activeProvider ? [{ key: 'Active', value: auth.activeProvider }] : []),
|
|
582
|
+
],
|
|
583
|
+
});
|
|
584
|
+
const roleRows = [];
|
|
585
|
+
try {
|
|
586
|
+
const projectConfig = await loadProjectConfig(process.cwd());
|
|
587
|
+
for (const [role, r] of Object.entries(projectConfig.roles)) {
|
|
588
|
+
if (!r)
|
|
589
|
+
continue;
|
|
590
|
+
const fb = r.fallback ? ` → fallback: ${r.fallback.cmd}` : '';
|
|
591
|
+
roleRows.push({ key: role, value: r.cmd + fb });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// fallback to cliConfig if no project config
|
|
596
|
+
for (const [role, r] of Object.entries(cliConfig.roles)) {
|
|
597
|
+
const fb = r.fallbackCli ? ` → ${r.fallbackCli}/${r.fallbackModel}` : '';
|
|
598
|
+
roleRows.push({ key: role, value: `${r.cli} -m ${r.model}${fb}` });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (cliConfig.fallback_global) {
|
|
602
|
+
roleRows.push({ key: 'global fallback', value: `${cliConfig.fallback_global.cli}/${cliConfig.fallback_global.model}` });
|
|
603
|
+
}
|
|
604
|
+
sections.push({ title: 'Roles — full cmd', rows: roleRows });
|
|
605
|
+
const dir = process.cwd();
|
|
606
|
+
try {
|
|
607
|
+
const config = await loadProjectConfig(dir);
|
|
608
|
+
const projectRows = [
|
|
609
|
+
{ key: 'Name', value: config.project },
|
|
610
|
+
{ key: 'Stack', value: config.stack },
|
|
611
|
+
];
|
|
612
|
+
const tasksDir = path.join(dir, '.agent', 'tasks');
|
|
613
|
+
const tasks = await listDir(tasksDir);
|
|
614
|
+
if (tasks.length > 0) {
|
|
615
|
+
const taskList = tasks.sort().reverse().slice(0, 5).map((t) => {
|
|
616
|
+
try {
|
|
617
|
+
const progress = JSON.parse(fsSync.readFileSync(path.join(tasksDir, t, 'progress.json'), 'utf-8'));
|
|
618
|
+
const s = progress?.status || '?';
|
|
619
|
+
const icon = s === 'completed' ? '✓' : s === 'planned' ? '○' : '✗';
|
|
620
|
+
return `${icon} ${t}`;
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return `? ${t}`;
|
|
624
|
+
}
|
|
625
|
+
}).join(' ');
|
|
626
|
+
projectRows.push({ key: 'Tasks', value: `${tasks.length} total ${taskList}` });
|
|
627
|
+
}
|
|
628
|
+
sections.push({ title: 'Project', rows: projectRows });
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
sections.push({ title: 'Project', rows: [{ key: 'Config', value: 'not found — run /setup' }] });
|
|
632
|
+
}
|
|
633
|
+
fi.println('');
|
|
634
|
+
fi.println(renderMultiSectionBox(sections));
|
|
635
|
+
fi.println('');
|
|
636
|
+
}
|
|
637
|
+
async function cmdModels(provider, fi) {
|
|
638
|
+
const installed = detectInstalledClis();
|
|
639
|
+
if (provider) {
|
|
640
|
+
const info = CLI_REGISTRY[provider];
|
|
641
|
+
if (!info) {
|
|
642
|
+
fi.println(chalk.red(` Unknown: ${provider}`));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const models = detectModels(provider);
|
|
646
|
+
fi.println(chalk.bold(`\n ${info.name} models:`));
|
|
647
|
+
for (const m of models)
|
|
648
|
+
fi.println(` ${m}`);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
for (const c of installed) {
|
|
652
|
+
const models = detectModels(c.name);
|
|
653
|
+
fi.println(chalk.bold(`\n ${c.name} (${c.path}):`));
|
|
654
|
+
for (const m of models.slice(0, 10))
|
|
655
|
+
fi.println(` ${m}`);
|
|
656
|
+
if (models.length > 10)
|
|
657
|
+
fi.println(chalk.dim(` ... and ${models.length - 10} more`));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
fi.println('');
|
|
661
|
+
}
|
|
662
|
+
async function cmdAuthStatus(fi) {
|
|
663
|
+
const auth = await loadAuth();
|
|
664
|
+
const rows = auth.entries.length === 0
|
|
665
|
+
? [{ key: 'Status', value: 'Not logged in' }]
|
|
666
|
+
: auth.entries.map((e) => ({
|
|
667
|
+
key: e.provider,
|
|
668
|
+
value: `${e.method}${e.provider === auth.activeProvider ? ' (active)' : ''}${e.email ? ` ${e.email}` : ''}`,
|
|
669
|
+
}));
|
|
670
|
+
fi.println(renderSectionBox('Auth Status', rows));
|
|
671
|
+
}
|
|
672
|
+
function cmdHelp(fi) {
|
|
673
|
+
const commands = [
|
|
674
|
+
{ key: '/setup', value: 'Full interactive setup wizard' },
|
|
675
|
+
{ key: '/setup orch', value: 'Reconfigure orchestrator only' },
|
|
676
|
+
{ key: '/setup impl', value: 'Reconfigure implementor only' },
|
|
677
|
+
{ key: '/setup rev', value: 'Reconfigure reviewer only' },
|
|
678
|
+
{ key: '/setup explorer', value: 'Reconfigure explorer only' },
|
|
679
|
+
{ key: '/config-multi', value: 'Reconfigure all agents at once' },
|
|
680
|
+
{ key: '/status', value: 'Show current configuration and tasks' },
|
|
681
|
+
{ key: '/run <task>', value: 'Full cycle: orchestrator → implementor → reviewer' },
|
|
682
|
+
{ key: '/run orch <task>', value: 'Run only orchestrator' },
|
|
683
|
+
{ key: '/run impl <id>', value: 'Run only implementor' },
|
|
684
|
+
{ key: '/run rev <id>', value: 'Run only reviewer' },
|
|
685
|
+
{ key: '/models', value: 'List models for all installed CLIs' },
|
|
686
|
+
{ key: '/models <cli>', value: 'List models for a specific CLI' },
|
|
687
|
+
{ key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
|
|
688
|
+
{ key: '/logout', value: 'Logout and clear credentials' },
|
|
689
|
+
{ key: '/auth-status', value: 'Show authentication status' },
|
|
690
|
+
{ key: '/tasks', value: 'List all tasks' },
|
|
691
|
+
{ key: '/clear', value: 'Clear the screen' },
|
|
692
|
+
{ key: '/help', value: 'Show this help' },
|
|
693
|
+
{ key: '/exit', value: 'Exit agent-mp' },
|
|
694
|
+
];
|
|
695
|
+
fi.println(renderSectionBox('Commands', commands));
|
|
696
|
+
}
|
|
697
|
+
async function cmdTasks(fi) {
|
|
698
|
+
const dir = process.cwd();
|
|
699
|
+
const tasksDir = path.join(dir, '.agent', 'tasks');
|
|
700
|
+
const tasks = await listDir(tasksDir);
|
|
701
|
+
if (tasks.length === 0) {
|
|
702
|
+
fi.println(renderSectionBox('Tasks', [{ key: '—', value: 'No tasks yet' }]));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const rows = [];
|
|
706
|
+
for (const t of tasks.sort().reverse()) {
|
|
707
|
+
try {
|
|
708
|
+
const progress = await readJson(path.join(tasksDir, t, 'progress.json'));
|
|
709
|
+
const s = progress?.status || 'unknown';
|
|
710
|
+
const icon = s === 'completed' ? '✓' : s === 'planned' ? '○' : '✗';
|
|
711
|
+
rows.push({ key: `${icon} ${t}`, value: s });
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
rows.push({ key: `? ${t}`, value: 'unknown' });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
fi.println(renderSectionBox('Tasks', rows));
|
|
718
|
+
}
|
|
719
|
+
// Coordinator CLI for clarification phase
|
|
720
|
+
let gCoordinatorCmd = '';
|
|
721
|
+
/**
|
|
722
|
+
* Shared coordinator setup: detect CLIs, pick coordinator, check auth.
|
|
723
|
+
* Returns { coordinatorCmd, activeCli, installed, rl } or null if setup failed.
|
|
724
|
+
*/
|
|
725
|
+
export async function initCoordinator() {
|
|
726
|
+
const rl = readline.createInterface({
|
|
727
|
+
input: process.stdin,
|
|
728
|
+
output: process.stdout,
|
|
729
|
+
});
|
|
730
|
+
// Step 1: Detect installed CLIs
|
|
731
|
+
const installed = detectInstalledClis();
|
|
732
|
+
if (installed.length === 0) {
|
|
733
|
+
console.log(chalk.red(' No CLIs detected. Install at least one: qwen, claude, gemini, codex'));
|
|
734
|
+
rl.close();
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
// Step 2: Pick coordinator
|
|
738
|
+
const auth = await loadAuth();
|
|
739
|
+
const currentAuth = auth.activeProvider;
|
|
740
|
+
let activeCli = installed.find((c) => c.name === currentAuth);
|
|
741
|
+
// Check if corporate credentials exist for qwen
|
|
742
|
+
const hasCorporateCreds = (() => {
|
|
743
|
+
if (currentAuth === 'qwen') {
|
|
744
|
+
const corporateCreds = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
|
|
745
|
+
try {
|
|
746
|
+
const stats = fsSync.statSync(corporateCreds);
|
|
747
|
+
return stats.isFile();
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return true;
|
|
754
|
+
})();
|
|
755
|
+
// If no active provider OR no corporate credentials, show selector
|
|
756
|
+
if (!activeCli || !hasCorporateCreds) {
|
|
757
|
+
// No coordinator configured - show selector
|
|
758
|
+
const qwenInstalled = installed.find((c) => c.name === 'qwen');
|
|
759
|
+
if (!qwenInstalled) {
|
|
760
|
+
console.log(chalk.red(' Qwen CLI not found. Install with: npm install -g @qwen-code/qwen-code'));
|
|
761
|
+
rl.close();
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
// Show selector (even if only one option)
|
|
765
|
+
const selectedName = await selectOption(rl, 'Select AI service for coordinator:', ['qwen']);
|
|
766
|
+
activeCli = qwenInstalled;
|
|
767
|
+
auth.activeProvider = 'qwen';
|
|
768
|
+
auth.entries = auth.entries.filter((e) => e.provider !== 'qwen');
|
|
769
|
+
auth.entries.push({ provider: 'qwen', method: 'oauth' });
|
|
770
|
+
await saveAuth(auth);
|
|
771
|
+
}
|
|
772
|
+
// If activeCli is already set, skip selection and use existing coordinator
|
|
773
|
+
console.log(chalk.dim(`\n Checking ${activeCli.name} auth...`));
|
|
774
|
+
let authResult = await checkCliAuth(activeCli.name);
|
|
775
|
+
if (!authResult.ok) {
|
|
776
|
+
console.log(chalk.yellow(` ${activeCli.name} session not found.`));
|
|
777
|
+
console.log(chalk.blue(` Opening browser for ${activeCli.name} login...`));
|
|
778
|
+
console.log(chalk.dim(' (Use your corporate account)\n'));
|
|
779
|
+
const authCmd = CLI_AUTH_COMMANDS[activeCli.name];
|
|
780
|
+
if (authCmd) {
|
|
781
|
+
try {
|
|
782
|
+
// For qwen, do corporate login flow
|
|
783
|
+
if (activeCli.name === 'qwen') {
|
|
784
|
+
const personalCreds = path.join(os.homedir(), '.qwen', 'oauth_creds.json');
|
|
785
|
+
const corporateCreds = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
|
|
786
|
+
let personalBackup = null;
|
|
787
|
+
if (await fileExists(personalCreds)) {
|
|
788
|
+
personalBackup = await fs.readFile(personalCreds, 'utf-8');
|
|
789
|
+
await fs.unlink(personalCreds);
|
|
790
|
+
}
|
|
791
|
+
await fs.unlink(corporateCreds).catch(() => { });
|
|
792
|
+
execSync('qwen auth qwen-oauth', { stdio: 'inherit' });
|
|
793
|
+
if (await fileExists(personalCreds)) {
|
|
794
|
+
const newCreds = await fs.readFile(personalCreds, 'utf-8');
|
|
795
|
+
await fs.writeFile(corporateCreds, newCreds);
|
|
796
|
+
}
|
|
797
|
+
if (personalBackup) {
|
|
798
|
+
await fs.writeFile(personalCreds, personalBackup);
|
|
799
|
+
}
|
|
800
|
+
authResult = await checkCliAuth(activeCli.name);
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
execSync(authCmd, { stdio: 'inherit' });
|
|
804
|
+
authResult = await checkCliAuth(activeCli.name);
|
|
805
|
+
}
|
|
806
|
+
if (authResult.ok) {
|
|
807
|
+
console.log(chalk.green(` ${activeCli.name} authenticated successfully\n`));
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
console.log(chalk.red(` ${activeCli.name} auth failed. Try /login.\n`));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
console.log(chalk.yellow(` Login interrupted. Type /login to retry.\n`));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
const emailStr = authResult.email ? chalk.dim(` (${authResult.email})`) : '';
|
|
820
|
+
console.log(chalk.green(` ${activeCli.name} session active${emailStr}\n`));
|
|
821
|
+
}
|
|
822
|
+
// Save email to auth store if we have it
|
|
823
|
+
if (authResult.email) {
|
|
824
|
+
const auth2 = await loadAuth();
|
|
825
|
+
const entry = auth2.entries.find((e) => e.provider === activeCli.name);
|
|
826
|
+
if (entry)
|
|
827
|
+
entry.email = authResult.email;
|
|
828
|
+
await saveAuth(auth2);
|
|
829
|
+
}
|
|
830
|
+
// Build coordinator command using the ACTIVE CLI (not orchestrator)
|
|
831
|
+
// The coordinator converses naturally using whatever CLI is active (qwen, claude, etc.)
|
|
832
|
+
const activeModel = detectModels(activeCli.name)[0] || 'default';
|
|
833
|
+
gCoordinatorCmd = buildCmd(activeCli.name, activeModel);
|
|
834
|
+
return { coordinatorCmd: gCoordinatorCmd, activeCli, installed, rl };
|
|
835
|
+
}
|
|
836
|
+
/** REPL mode — interactive loop */
|
|
837
|
+
export async function runRepl(resumeSession) {
|
|
838
|
+
const init = await initCoordinator();
|
|
839
|
+
if (!init)
|
|
840
|
+
return;
|
|
841
|
+
const { activeCli } = init;
|
|
842
|
+
const rl = init.rl;
|
|
843
|
+
const session = resumeSession ?? newSession(process.cwd());
|
|
844
|
+
// Show project status and FORCE role configuration if missing
|
|
845
|
+
let hasRoles = false;
|
|
846
|
+
let welcomeRoles;
|
|
847
|
+
let welcomeProject = path.basename(process.cwd());
|
|
848
|
+
let welcomeStack = 'generic';
|
|
849
|
+
try {
|
|
850
|
+
const config = await loadProjectConfig(process.cwd());
|
|
851
|
+
welcomeProject = config.project;
|
|
852
|
+
welcomeStack = config.stack;
|
|
853
|
+
if (config.roles?.orchestrator?.cli && config.roles?.implementor?.cli && config.roles?.reviewer?.cli) {
|
|
854
|
+
hasRoles = true;
|
|
855
|
+
welcomeRoles = {
|
|
856
|
+
orchestrator: `${config.roles.orchestrator.cli}/${config.roles.orchestrator.model}`,
|
|
857
|
+
implementor: `${config.roles.implementor.cli}/${config.roles.implementor.model}`,
|
|
858
|
+
reviewer: `${config.roles.reviewer.cli}/${config.roles.reviewer.model}`,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch { }
|
|
863
|
+
if (!hasRoles) {
|
|
864
|
+
console.log(chalk.yellow('\n Todos los roles deben estar configurados antes de trabajar.'));
|
|
865
|
+
console.log(chalk.yellow(' Configurando roles ahora...\n'));
|
|
866
|
+
await cmdConfigMulti(rl);
|
|
867
|
+
// Re-check
|
|
868
|
+
try {
|
|
869
|
+
const config = await loadProjectConfig(process.cwd());
|
|
870
|
+
welcomeProject = config.project;
|
|
871
|
+
welcomeStack = config.stack;
|
|
872
|
+
if (config.roles?.orchestrator?.cli && config.roles?.implementor?.cli && config.roles?.reviewer?.cli) {
|
|
873
|
+
hasRoles = true;
|
|
874
|
+
welcomeRoles = {
|
|
875
|
+
orchestrator: `${config.roles.orchestrator.cli}/${config.roles.orchestrator.model}`,
|
|
876
|
+
implementor: `${config.roles.implementor.cli}/${config.roles.implementor.model}`,
|
|
877
|
+
reviewer: `${config.roles.reviewer.cli}/${config.roles.reviewer.model}`,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
catch { }
|
|
882
|
+
if (!hasRoles) {
|
|
883
|
+
console.log(chalk.red('\n Error: roles no configurados. Saliendo.'));
|
|
884
|
+
rl.close();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// Print welcome panel + hint (before fixed input takes over)
|
|
889
|
+
process.stdout.write(renderWelcomePanel({
|
|
890
|
+
version: `${pkgInfo.name} v${pkgInfo.version}`,
|
|
891
|
+
cliName: pkgInfo.name,
|
|
892
|
+
activeModel: activeCli.name,
|
|
893
|
+
user: process.env.USER || process.env.LOGNAME || 'user',
|
|
894
|
+
project: welcomeProject,
|
|
895
|
+
stack: welcomeStack,
|
|
896
|
+
roles: welcomeRoles,
|
|
897
|
+
}) + '\n');
|
|
898
|
+
process.stdout.write(renderHelpHint() + '\n\n');
|
|
899
|
+
// Fixed-bottom input — owns the last 3 terminal rows permanently
|
|
900
|
+
const fi = new FixedInput();
|
|
901
|
+
fi.setup();
|
|
902
|
+
// If resuming, print past conversation history
|
|
903
|
+
if (resumeSession && resumeSession.messages.length > 0) {
|
|
904
|
+
const date = new Date(resumeSession.createdAt).toLocaleString();
|
|
905
|
+
fi.println(chalk.dim(` ─── Resuming session from ${date} (${resumeSession.messages.length} messages) ───`));
|
|
906
|
+
for (const msg of resumeSession.messages) {
|
|
907
|
+
if (msg.role === 'user') {
|
|
908
|
+
const lines = msg.content.split('\n').filter(l => l.trim());
|
|
909
|
+
for (let i = 0; i < lines.length; i++) {
|
|
910
|
+
const prefix = i === 0 ? chalk.dim('usuario: ') : ' ';
|
|
911
|
+
fi.println(`${prefix}${chalk.white(lines[i])}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
fi.println(chalk.cyan(msg.content.split('\n').map(l => ` ${l}`).join('\n')));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
fi.println(chalk.dim(' ─── Continue below ───'));
|
|
919
|
+
fi.println('');
|
|
920
|
+
}
|
|
921
|
+
// Helper: run a wizard command that needs readline (suspends fixed input)
|
|
922
|
+
const withRl = async (fn) => {
|
|
923
|
+
const resume = fi.suspend();
|
|
924
|
+
try {
|
|
925
|
+
await fn(rl);
|
|
926
|
+
}
|
|
927
|
+
finally {
|
|
928
|
+
resume();
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
// Main REPL loop
|
|
932
|
+
let firstMessage = true;
|
|
933
|
+
// eslint-disable-next-line no-constant-condition
|
|
934
|
+
while (true) {
|
|
935
|
+
const line = await fi.readLine();
|
|
936
|
+
const trimmed = line.trim();
|
|
937
|
+
if (!trimmed)
|
|
938
|
+
continue;
|
|
939
|
+
// Show coordinator header before the first user message
|
|
940
|
+
if (firstMessage && !trimmed.startsWith('/')) {
|
|
941
|
+
firstMessage = false;
|
|
942
|
+
try {
|
|
943
|
+
const dir = process.cwd();
|
|
944
|
+
const config = await loadProjectConfig(dir);
|
|
945
|
+
if (config.roles?.orchestrator?.cli) {
|
|
946
|
+
fi.println('');
|
|
947
|
+
fi.println(chalk.bold.cyan('═'.repeat(46)));
|
|
948
|
+
fi.println(chalk.bold.cyan(` COORDINADOR — ${config.project}`));
|
|
949
|
+
fi.println(chalk.bold.cyan('═'.repeat(46)));
|
|
950
|
+
fi.println(chalk.blue(` → Rol: COORDINADOR (solo orquesta, nunca ejecuta)`));
|
|
951
|
+
fi.println(chalk.dim(' ' + '─'.repeat(42)));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
catch { }
|
|
955
|
+
}
|
|
956
|
+
// Show user message in the scroll area (chat style, right-aligned per line)
|
|
957
|
+
const userLines = trimmed.split('\n').filter(l => l.trim());
|
|
958
|
+
for (let i = 0; i < userLines.length; i++) {
|
|
959
|
+
const prefix = (i === 0) ? chalk.dim('usuario: ') : ' ';
|
|
960
|
+
const msg = `${prefix}${chalk.white(userLines[i])}`;
|
|
961
|
+
const rawLen = msg.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
962
|
+
const padding = ' '.repeat(Math.max(0, fi.cols - rawLen - 2));
|
|
963
|
+
fi.println(`${padding}${msg}`);
|
|
964
|
+
}
|
|
965
|
+
if (!trimmed.startsWith('/')) {
|
|
966
|
+
session.messages.push({ role: 'user', content: trimmed, ts: new Date().toISOString() });
|
|
967
|
+
await saveSession(session);
|
|
968
|
+
try {
|
|
969
|
+
const dir = process.cwd();
|
|
970
|
+
const config = await loadProjectConfig(dir);
|
|
971
|
+
if (!config.roles?.orchestrator?.cli) {
|
|
972
|
+
fi.println(chalk.red(' No roles configured. Run /config-multi first.'));
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi);
|
|
976
|
+
await engine.runFullCycle(trimmed);
|
|
977
|
+
session.messages.push({ role: 'agent', content: `[task cycle completed for: ${trimmed}]`, ts: new Date().toISOString() });
|
|
978
|
+
await saveSession(session);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
catch (err) {
|
|
982
|
+
fi.println(chalk.red(` Error: ${err.message}`));
|
|
983
|
+
}
|
|
984
|
+
fi.redrawBox();
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
988
|
+
const cmd = parts[0].toLowerCase();
|
|
989
|
+
const args = parts.slice(1);
|
|
990
|
+
try {
|
|
991
|
+
switch (cmd) {
|
|
992
|
+
case 'setup': {
|
|
993
|
+
const sub = args[0]?.toLowerCase();
|
|
994
|
+
const roleMap = {
|
|
995
|
+
orch: 'orchestrator', impl: 'implementor', rev: 'reviewer',
|
|
996
|
+
explorer: 'explorer', proposer: 'proposer', critic: 'critic',
|
|
997
|
+
};
|
|
998
|
+
if (sub && roleMap[sub]) {
|
|
999
|
+
await withRl((rl) => cmdConfigOneRole(rl, roleMap[sub]));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
await withRl(cmdSetup);
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
case 'config-multi':
|
|
1007
|
+
await withRl(cmdConfigMulti);
|
|
1008
|
+
break;
|
|
1009
|
+
case 'status':
|
|
1010
|
+
await cmdStatus(fi);
|
|
1011
|
+
break;
|
|
1012
|
+
case 'run':
|
|
1013
|
+
if (args[0] === 'orch' || args[0] === 'orchestrator') {
|
|
1014
|
+
const task = args.slice(1).join(' ');
|
|
1015
|
+
const dir = process.cwd();
|
|
1016
|
+
const config = await loadProjectConfig(dir);
|
|
1017
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi);
|
|
1018
|
+
const result = await engine.runOrchestrator(task);
|
|
1019
|
+
fi.println(chalk.green(` Task ID: ${result.taskId}`));
|
|
1020
|
+
}
|
|
1021
|
+
else if (args[0] === 'impl' || args[0] === 'implementor') {
|
|
1022
|
+
const taskId = args[1];
|
|
1023
|
+
const dir = process.cwd();
|
|
1024
|
+
const config = await loadProjectConfig(dir);
|
|
1025
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi);
|
|
1026
|
+
const taskDir = path.join(dir, '.agent', 'tasks', taskId);
|
|
1027
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1028
|
+
await engine.runImplementor(taskId, plan);
|
|
1029
|
+
}
|
|
1030
|
+
else if (args[0] === 'rev' || args[0] === 'reviewer') {
|
|
1031
|
+
const taskId = args[1];
|
|
1032
|
+
const dir = process.cwd();
|
|
1033
|
+
const config = await loadProjectConfig(dir);
|
|
1034
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi);
|
|
1035
|
+
const taskDir = path.join(dir, '.agent', 'tasks', taskId);
|
|
1036
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1037
|
+
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1038
|
+
await engine.runReviewer(taskId, plan, progress);
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
const task = args.join(' ');
|
|
1042
|
+
const dir = process.cwd();
|
|
1043
|
+
const config = await loadProjectConfig(dir);
|
|
1044
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi);
|
|
1045
|
+
await engine.runFullCycle(task);
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
case 'models':
|
|
1049
|
+
await cmdModels(args[0], fi);
|
|
1050
|
+
break;
|
|
1051
|
+
case 'login':
|
|
1052
|
+
await withRl(async (rl) => { await cmdLogin(rl); });
|
|
1053
|
+
break;
|
|
1054
|
+
case 'logout': {
|
|
1055
|
+
// Clear OAuth credentials and auth store
|
|
1056
|
+
const credsPath = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
|
|
1057
|
+
await fs.unlink(credsPath).catch(() => { });
|
|
1058
|
+
const authStore = await loadAuth();
|
|
1059
|
+
authStore.entries = [];
|
|
1060
|
+
delete authStore.activeProvider;
|
|
1061
|
+
await saveAuth(authStore);
|
|
1062
|
+
fi.println(chalk.dim(' Logged out. Credentials cleared.'));
|
|
1063
|
+
fi.teardown();
|
|
1064
|
+
rl.close();
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
case 'auth-status':
|
|
1068
|
+
await cmdAuthStatus(fi);
|
|
1069
|
+
break;
|
|
1070
|
+
case 'tasks':
|
|
1071
|
+
await cmdTasks(fi);
|
|
1072
|
+
break;
|
|
1073
|
+
case 'clear':
|
|
1074
|
+
fi.teardown();
|
|
1075
|
+
console.clear();
|
|
1076
|
+
fi.setup();
|
|
1077
|
+
break;
|
|
1078
|
+
case 'help':
|
|
1079
|
+
cmdHelp(fi);
|
|
1080
|
+
break;
|
|
1081
|
+
case 'exit':
|
|
1082
|
+
case 'quit':
|
|
1083
|
+
fi.println(chalk.dim(' Bye!'));
|
|
1084
|
+
fi.teardown();
|
|
1085
|
+
rl.close();
|
|
1086
|
+
return;
|
|
1087
|
+
default:
|
|
1088
|
+
fi.println(chalk.red(` Unknown command: /${cmd}`));
|
|
1089
|
+
fi.println(chalk.dim(' Type /help for available commands'));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
catch (err) {
|
|
1093
|
+
fi.println(chalk.red(` Error: ${err.message}`));
|
|
1094
|
+
}
|
|
1095
|
+
// Always redraw box after each command
|
|
1096
|
+
fi.redrawBox();
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Direct role execution — agent-mp MISMO actúa como rol.
|
|
1101
|
+
* NO spawnea CLIs externos. Usa la conexión OAuth de qwen directamente.
|
|
1102
|
+
* Prompt estricto: cada rol solo hace lo que debe, sin extras.
|
|
1103
|
+
*/
|
|
1104
|
+
export async function runRole(role, arg) {
|
|
1105
|
+
const init = await initCoordinator();
|
|
1106
|
+
if (!init)
|
|
1107
|
+
process.exit(1);
|
|
1108
|
+
const { coordinatorCmd } = init;
|
|
1109
|
+
const rl = init.rl;
|
|
1110
|
+
const dir = process.cwd();
|
|
1111
|
+
let config;
|
|
1112
|
+
try {
|
|
1113
|
+
config = await loadProjectConfig(dir);
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
console.log(chalk.red(' No project config found. Run /setup first.'));
|
|
1117
|
+
rl.close();
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
// Validate roles are configured
|
|
1121
|
+
if (!config.roles?.orchestrator?.cli || !config.roles?.implementor?.cli || !config.roles?.reviewer?.cli) {
|
|
1122
|
+
console.log(chalk.red(' Roles not configured. Run setup first.'));
|
|
1123
|
+
rl.close();
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
console.log(chalk.bold.cyan(`\n Agent-mp — Rol: ${role.toUpperCase()}\n`));
|
|
1127
|
+
const engine = new AgentEngine(config, dir, coordinatorCmd, rl);
|
|
1128
|
+
try {
|
|
1129
|
+
switch (role.toLowerCase()) {
|
|
1130
|
+
case 'orchestrator':
|
|
1131
|
+
case 'orch': {
|
|
1132
|
+
const result = await engine.runOrchestrator(arg);
|
|
1133
|
+
log.ok(`Task ID: ${result.taskId}`);
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
case 'implementor':
|
|
1137
|
+
case 'impl': {
|
|
1138
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1139
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1140
|
+
await engine.runImplementor(arg, plan);
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
case 'reviewer':
|
|
1144
|
+
case 'rev': {
|
|
1145
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1146
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1147
|
+
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1148
|
+
await engine.runReviewer(arg, plan, progress);
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
case 'coordinator':
|
|
1152
|
+
case 'coord': {
|
|
1153
|
+
console.log(chalk.yellow(' Coordinator mode requires interactive REPL.'));
|
|
1154
|
+
console.log(chalk.yellow(' Run `agent-mp` without --rol to start the interactive loop.'));
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
default:
|
|
1158
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer`));
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
catch (err) {
|
|
1163
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
1164
|
+
process.exit(1);
|
|
1165
|
+
}
|
|
1166
|
+
rl.close();
|
|
1167
|
+
}
|