agent-mp 0.3.7 → 0.3.9
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/core/engine.d.ts +2 -0
- package/dist/core/engine.js +37 -3
- package/dist/ui/input.d.ts +5 -0
- package/dist/ui/input.js +17 -2
- package/package.json +1 -1
package/dist/core/engine.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export declare class AgentEngine {
|
|
|
16
16
|
private totalTokens;
|
|
17
17
|
private phaseTokens;
|
|
18
18
|
constructor(config: AgentConfig, projectDir: string, coordinatorCmd?: string, rl?: readline.Interface, fi?: FixedInput, slashHandler?: SlashHandler);
|
|
19
|
+
/** Start an animated spinner on the status row. Returns a stop function. */
|
|
20
|
+
private _startSpinner;
|
|
19
21
|
/**
|
|
20
22
|
* FASE 0 — Clarificacion con el programador.
|
|
21
23
|
* El coordinador (CLI activo, ej: Qwen) conversa con el usuario
|
package/dist/core/engine.js
CHANGED
|
@@ -219,6 +219,20 @@ export class AgentEngine {
|
|
|
219
219
|
this.fi = fi;
|
|
220
220
|
this.slashHandler = slashHandler;
|
|
221
221
|
}
|
|
222
|
+
/** Start an animated spinner on the status row. Returns a stop function. */
|
|
223
|
+
_startSpinner(label) {
|
|
224
|
+
if (!this.fi)
|
|
225
|
+
return () => { };
|
|
226
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
227
|
+
let i = 0;
|
|
228
|
+
const t0 = Date.now();
|
|
229
|
+
const fi = this.fi;
|
|
230
|
+
const iv = setInterval(() => {
|
|
231
|
+
const s = Math.floor((Date.now() - t0) / 1000);
|
|
232
|
+
fi.setStatus(` ${frames[i++ % frames.length]} ${label} ${s}s`);
|
|
233
|
+
}, 100);
|
|
234
|
+
return () => { clearInterval(iv); fi.setStatus(null); };
|
|
235
|
+
}
|
|
222
236
|
/**
|
|
223
237
|
* FASE 0 — Clarificacion con el programador.
|
|
224
238
|
* El coordinador (CLI activo, ej: Qwen) conversa con el usuario
|
|
@@ -253,11 +267,15 @@ INSTRUCCIONES:
|
|
|
253
267
|
if (this.coordinatorCmd.startsWith('qwen')) {
|
|
254
268
|
// Use Qwen API directly — avoids the qwen CLI's own OAuth flow
|
|
255
269
|
// which causes mid-session auth popups and breaks display.
|
|
270
|
+
const model = this.coordinatorCmd.match(/(?:-m|--model)\s+(\S+)/)?.[1] || 'coder-model';
|
|
271
|
+
const stopSpin = this._startSpinner(`coordinador ${model}`);
|
|
256
272
|
try {
|
|
257
|
-
const
|
|
258
|
-
|
|
273
|
+
const result = await callQwenAPI(prompt, model);
|
|
274
|
+
stopSpin();
|
|
275
|
+
return result;
|
|
259
276
|
}
|
|
260
277
|
catch (err) {
|
|
278
|
+
stopSpin();
|
|
261
279
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
262
280
|
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
263
281
|
console.log(chalk.yellow(' Ejecutá: /login para re-autenticarte.\n'));
|
|
@@ -269,7 +287,9 @@ INSTRUCCIONES:
|
|
|
269
287
|
}
|
|
270
288
|
}
|
|
271
289
|
else {
|
|
290
|
+
const stopSpin = this._startSpinner(`coordinador`);
|
|
272
291
|
res = await runCli(this.coordinatorCmd, prompt, 600000, envOverride);
|
|
292
|
+
stopSpin();
|
|
273
293
|
}
|
|
274
294
|
// Extract readable text — search for JSON array even if there's prefix text
|
|
275
295
|
let responseText = res.output.trim();
|
|
@@ -366,15 +386,19 @@ INSTRUCCIONES:
|
|
|
366
386
|
const rolePrompt = this.buildRolePrompt(roleName, prompt);
|
|
367
387
|
/** Try a cmd, and if it fails, auto-detect flags from --help and retry */
|
|
368
388
|
const tryWithAutoRepair = async (cliName, model, currentCmd) => {
|
|
389
|
+
const stopSpin = this._startSpinner(`${cliName} ${model}`);
|
|
369
390
|
try {
|
|
370
391
|
const result = await runCli(currentCmd, rolePrompt);
|
|
371
392
|
if (result.exitCode !== 0) {
|
|
393
|
+
stopSpin();
|
|
372
394
|
const detail = result.output.trim().slice(0, 500);
|
|
373
395
|
throw new Error(`${cliName} exited with code ${result.exitCode}${detail ? `\n${detail}` : ''}`);
|
|
374
396
|
}
|
|
397
|
+
stopSpin();
|
|
375
398
|
return result.output;
|
|
376
399
|
}
|
|
377
400
|
catch (err) {
|
|
401
|
+
stopSpin();
|
|
378
402
|
log.warn(`${cliName} failed, detecting flags from --help: ${err.message}`);
|
|
379
403
|
const detected = detectCliFlags(cliName);
|
|
380
404
|
if (Object.keys(detected).length === 0) {
|
|
@@ -426,8 +450,10 @@ INSTRUCCIONES:
|
|
|
426
450
|
// Config file might not exist yet
|
|
427
451
|
}
|
|
428
452
|
// Retry with new command
|
|
453
|
+
const stopSpin2 = this._startSpinner(`${cliName} ${model} (retry)`);
|
|
429
454
|
try {
|
|
430
455
|
const result = await runCli(newCmd, rolePrompt);
|
|
456
|
+
stopSpin2();
|
|
431
457
|
if (result.exitCode !== 0) {
|
|
432
458
|
const detail = result.output.trim().slice(0, 500);
|
|
433
459
|
throw new Error(`${cliName} (repaired) exited with code ${result.exitCode}${detail ? `\n${detail}` : ''}`);
|
|
@@ -436,6 +462,7 @@ INSTRUCCIONES:
|
|
|
436
462
|
return result.output;
|
|
437
463
|
}
|
|
438
464
|
catch (retryErr) {
|
|
465
|
+
stopSpin2();
|
|
439
466
|
log.warn(`Repaired ${cliName} also failed: ${retryErr.message}`);
|
|
440
467
|
return null;
|
|
441
468
|
}
|
|
@@ -452,11 +479,15 @@ INSTRUCCIONES:
|
|
|
452
479
|
log.warn(`${cliName} has no credentials — run: ${cliName} --login`);
|
|
453
480
|
return null;
|
|
454
481
|
}
|
|
482
|
+
const stopSpin = this._startSpinner(`${cliName} ${model}`);
|
|
455
483
|
try {
|
|
456
484
|
log.info(`${cliName}: calling Qwen API with own credentials (${model})`);
|
|
457
|
-
|
|
485
|
+
const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath);
|
|
486
|
+
stopSpin();
|
|
487
|
+
return result;
|
|
458
488
|
}
|
|
459
489
|
catch (err) {
|
|
490
|
+
stopSpin();
|
|
460
491
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
461
492
|
console.log(chalk.red(`\n ✗ Sesión expirada para ${cliName}.`));
|
|
462
493
|
console.log(chalk.yellow(` Ejecutá: ${cliName} --login\n`));
|
|
@@ -854,10 +885,13 @@ INSTRUCCIONES:
|
|
|
854
885
|
4. Crea/actualiza ${archPath} con tabla resumen y detalle por servicio.
|
|
855
886
|
5. Crea/actualiza ${contextDir}/<servicio>/architecture.md para cada servicio.`);
|
|
856
887
|
let result;
|
|
888
|
+
const stopSpin = this._startSpinner(`agent-explorer ${role.model}`);
|
|
857
889
|
try {
|
|
858
890
|
result = await callQwenAPI(prompt, role.model);
|
|
891
|
+
stopSpin();
|
|
859
892
|
}
|
|
860
893
|
catch (err) {
|
|
894
|
+
stopSpin();
|
|
861
895
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
862
896
|
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
863
897
|
console.log(chalk.yellow(' Ejecutá: agent-mp --login (o agent-explorer --login)\n'));
|
package/dist/ui/input.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare class FixedInput {
|
|
|
6
6
|
private _pasting;
|
|
7
7
|
private _pasteAccum;
|
|
8
8
|
private _drawPending;
|
|
9
|
+
private _statusText;
|
|
9
10
|
private get rows();
|
|
10
11
|
get cols(): number;
|
|
11
12
|
private get scrollBottom();
|
|
@@ -13,10 +14,14 @@ export declare class FixedInput {
|
|
|
13
14
|
setup(): void;
|
|
14
15
|
teardown(): void;
|
|
15
16
|
redrawBox(): void;
|
|
17
|
+
/** Show or clear the status / spinner line above the input box. */
|
|
18
|
+
setStatus(text: string | null): void;
|
|
16
19
|
suspend(): () => void;
|
|
17
20
|
readLine(): Promise<string>;
|
|
18
21
|
println(text: string): void;
|
|
19
22
|
printSeparator(): void;
|
|
23
|
+
/** Repaint the status row (between scroll region and input box). */
|
|
24
|
+
private _drawStatusRow;
|
|
20
25
|
/** Debounced draw: coalesces rapid calls (e.g. during paste) into a single repaint. */
|
|
21
26
|
private _scheduleDraw;
|
|
22
27
|
/** Set DECSTBM once — only called in setup() and on resize, never during typing. */
|
package/dist/ui/input.js
CHANGED
|
@@ -6,9 +6,9 @@ const PREFIX = T('│') + B(' > ');
|
|
|
6
6
|
const PREFIX_CONT = T('│') + B(' '); // continuation lines
|
|
7
7
|
const PREFIX_COLS = 4; // visual width of "│ > " and "│ "
|
|
8
8
|
// Maximum content rows the box can grow to (Shift+Enter / word-wrap).
|
|
9
|
-
// The reserved area at the bottom is MAX_CONTENT_ROWS + 2 (borders).
|
|
9
|
+
// The reserved area at the bottom is MAX_CONTENT_ROWS + 2 (borders) + 1 (status row).
|
|
10
10
|
const MAX_CONTENT_ROWS = 4;
|
|
11
|
-
const RESERVED_ROWS = MAX_CONTENT_ROWS +
|
|
11
|
+
const RESERVED_ROWS = MAX_CONTENT_ROWS + 3; // 7 = 4 content + 2 borders + 1 status
|
|
12
12
|
// ─── FixedInput ──────────────────────────────────────────────────────────────
|
|
13
13
|
// Keeps an input box pinned to the physical bottom of the terminal.
|
|
14
14
|
// The box starts as 3 rows (border + 1 content + border) and grows up to
|
|
@@ -23,6 +23,7 @@ export class FixedInput {
|
|
|
23
23
|
_pasting = false; // true while inside bracketed paste sequence
|
|
24
24
|
_pasteAccum = ''; // accumulates paste content between \x1b[200~ and \x1b[201~
|
|
25
25
|
_drawPending = false; // debounce flag
|
|
26
|
+
_statusText = ''; // spinner / status line above the input box
|
|
26
27
|
get rows() { return process.stdout.rows || 24; }
|
|
27
28
|
get cols() { return process.stdout.columns || 80; }
|
|
28
29
|
// The scroll region always ends here — everything below is reserved for the box.
|
|
@@ -65,6 +66,11 @@ export class FixedInput {
|
|
|
65
66
|
process.stdout.write(`\x1b[${this.rows};1H\n`);
|
|
66
67
|
}
|
|
67
68
|
redrawBox() { this._drawBox(); }
|
|
69
|
+
/** Show or clear the status / spinner line above the input box. */
|
|
70
|
+
setStatus(text) {
|
|
71
|
+
this._statusText = text || '';
|
|
72
|
+
this._drawBox();
|
|
73
|
+
}
|
|
68
74
|
suspend() {
|
|
69
75
|
console.log = this.origLog;
|
|
70
76
|
process.stdout.write('\x1b[?2004l'); // disable bracketed paste while suspended
|
|
@@ -201,6 +207,14 @@ export class FixedInput {
|
|
|
201
207
|
this.println(chalk.rgb(0, 120, 116)('─'.repeat(this.cols - 1)));
|
|
202
208
|
}
|
|
203
209
|
// ── Private drawing ────────────────────────────────────────────────────────
|
|
210
|
+
/** Repaint the status row (between scroll region and input box). */
|
|
211
|
+
_drawStatusRow() {
|
|
212
|
+
const row = this.scrollBottom + 1;
|
|
213
|
+
process.stdout.write(`\x1b[${row};1H\x1b[2K`);
|
|
214
|
+
if (this._statusText) {
|
|
215
|
+
process.stdout.write(chalk.rgb(0, 185, 180)(this._statusText));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
204
218
|
/** Debounced draw: coalesces rapid calls (e.g. during paste) into a single repaint. */
|
|
205
219
|
_scheduleDraw() {
|
|
206
220
|
if (this._drawPending)
|
|
@@ -235,6 +249,7 @@ export class FixedInput {
|
|
|
235
249
|
process.stdout.write('\x1b[?25l');
|
|
236
250
|
// Clear entire reserved area (removes stale content from previous draws)
|
|
237
251
|
this._clearReserved();
|
|
252
|
+
this._drawStatusRow();
|
|
238
253
|
// ── Top border ───────────────────────────────────────────────
|
|
239
254
|
process.stdout.write(`\x1b[${topBorder};1H`);
|
|
240
255
|
process.stdout.write(T('╭') + T('─'.repeat(cols - 2)));
|