agent-mp 0.3.7 → 0.3.8

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.
@@ -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
@@ -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 model = this.coordinatorCmd.match(/(?:-m|--model)\s+(\S+)/)?.[1] || 'coder-model';
258
- return await callQwenAPI(prompt, model);
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
- return await callQwenAPIFromCreds(rolePrompt, model, credsPath);
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'));
@@ -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 + 2; // 6
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._drawStatusRow();
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.dim(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)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-mp",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Deterministic multi-agent CLI orchestrator — plan, code, review",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",