chocolatito-code 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Las secciones, de arriba abajo. El orden es fijo a proposito: si cada quien
3
+ * pintara donde le tocara, dos productores independientes -el razonamiento y el
4
+ * spinner llegan por caminos distintos- se turnarian el sitio y el pie bailaria.
5
+ */
6
+ declare const ORDEN: readonly ["razonamiento", "spinner", "entrada"];
7
+ export type SeccionDelPie = (typeof ORDEN)[number];
8
+ /**
9
+ * Enciende o apaga el pie a mano. Con `null` vuelve a mandar el terminal.
10
+ *
11
+ * Las pruebas lo encienden porque en `node --test` no hay TTY y sin esto el pie
12
+ * seria un no-op silencioso: pasarian todas sin comprobar nada.
13
+ */
14
+ export declare function habilitarPie(valor: boolean | null): void;
15
+ /** ¿Hay algo pintado abajo que haya que respetar? */
16
+ export declare function hayPie(): boolean;
17
+ /**
18
+ * Pone (o reemplaza) una seccion del pie y repinta.
19
+ *
20
+ * Con la lista vacia la seccion desaparece, que es lo mismo que quitarla. Si el
21
+ * contenido es identico al que ya habia, no se repinta: el spinner llama ocho
22
+ * veces por segundo y la barra de escribir casi nunca cambia entre dos.
23
+ */
24
+ export declare function fijarSeccion(nombre: SeccionDelPie, lineas: string[]): void;
25
+ /**
26
+ * Varias secciones de una vez, con UN solo repintado.
27
+ *
28
+ * El spinner cambia su linea y la barra de escribir en el mismo tick. Puestas
29
+ * por separado se pintaba el bloque dos veces cada 120 ms, y la segunda pasada
30
+ * borraba y reescribia lo que la primera acababa de dejar: el doble de bytes
31
+ * para el mismo resultado, y un parpadeo que se nota en un terminal lento.
32
+ */
33
+ export declare function fijarSecciones(entradas: Array<[SeccionDelPie, string[]]>): void;
34
+ export declare function quitarSeccion(nombre: SeccionDelPie): void;
35
+ /**
36
+ * Escribe texto permanente sin que el pie se lo lleve por delante.
37
+ *
38
+ * Sin pie es un write normal y no cuesta nada; por eso puede sustituir a
39
+ * `console.log` en el camino del agente sin pensarlo dos veces.
40
+ */
41
+ export declare function escribirEncimaDelPie(texto: string): void;
42
+ /**
43
+ * Quita el pie entero y deja el terminal como si nunca hubiera estado.
44
+ *
45
+ * Lo que quede a medias de linea se conserva en pantalla: es texto del agente,
46
+ * no adorno, y borrarlo seria perder salida.
47
+ */
48
+ export declare function soltarPie(): void;
49
+ /**
50
+ * Olvida el estado sin tocar la pantalla.
51
+ *
52
+ * Para las pruebas y para la salida del proceso: ahi no queda terminal al que
53
+ * repintar, pero el cursor escondido si sobrevive y hay que devolverlo.
54
+ */
55
+ export declare function reiniciarPie(): void;
56
+ export {};
@@ -0,0 +1,309 @@
1
+ import process from "node:process";
2
+ import { dibujar } from "./salida.js";
3
+ /**
4
+ * EL PIE QUE NO SE VA CON EL SCROLL
5
+ *
6
+ * EL PROBLEMA
7
+ *
8
+ * El spinner y la linea de razonamiento se pintaban donde estuviera el cursor:
9
+ *
10
+ * process.stdout.write(`\r\x1b[2K${glyph} ${verb} (${segundos}s)`);
11
+ *
12
+ * Eso escribe en la fila actual y ahi se queda. En cuanto llega la respuesta,
13
+ * cada linea nueva empuja el contenido hacia arriba y se lleva el spinner con
14
+ * ella. El usuario lo describio mejor que ninguna documentacion: "la nubecita y
15
+ * el rayito suben con lo que me respondes". Y lo mismo la barra de escribir.
16
+ *
17
+ * El resultado es que mientras el modelo habla no hay NADA fijo en pantalla: ni
18
+ * cuanto lleva, ni cuantos tokens, ni donde escribir para encolar algo. Todo
19
+ * queda enterrado unas pantallas mas arriba.
20
+ *
21
+ * LA IDEA
22
+ *
23
+ * Un terminal no tiene "zonas". Se puede pedir una con la region de scroll
24
+ * (DECSTBM), pero lo que sale por arriba de una region asi NO entra en el
25
+ * historial del terminal en la mayoria de emuladores: se pierde. En un agente
26
+ * de codigo, donde media sesion consiste en subir a releer lo que salio, eso es
27
+ * inaceptable.
28
+ *
29
+ * Asi que se hace al reves, sin regiones: el pie se BORRA antes de cada
30
+ * escritura permanente y se REPINTA justo despues. El texto permanente se
31
+ * escribe con saltos de linea normales, entra en el historial como siempre, y
32
+ * el pie acaba de nuevo abajo del todo. Visto desde fuera, el pie no se mueve.
33
+ *
34
+ * LA INVARIANTE QUE LO SOSTIENE
35
+ *
36
+ * Tras pintar, el cursor queda al final de la ultima fila del bloque. Con eso
37
+ * basta para borrarlo entero: subir `filasPintadas - 1` filas, ir a la columna
38
+ * cero y borrar hacia abajo. Se cuentan filas FISICAS (lo que el terminal parte
39
+ * al llegar al ancho), no elementos del array: contar lineas logicas es
40
+ * exactamente el fallo que dejaba barras huerfanas en el prompt.
41
+ *
42
+ * Y LA SEGUNDA, LA QUE EVITA CORROMPER LA SALIDA AJENA
43
+ *
44
+ * Sin secciones puestas, el pie no pinta nada y la pantalla queda byte a byte
45
+ * como la habria dejado un `console.log` normal. Por eso el resto del programa
46
+ * -las herramientas, el renderizador, los avisos- puede seguir escribiendo
47
+ * directo sin saber que esto existe. Solo hay que pasar por aqui mientras haya
48
+ * pie, que es justo el rato que el bucle controla toda la salida.
49
+ */
50
+ const OCULTAR_CURSOR = "\x1b[?25l";
51
+ const MOSTRAR_CURSOR = "\x1b[?25h";
52
+ /**
53
+ * Las secciones, de arriba abajo. El orden es fijo a proposito: si cada quien
54
+ * pintara donde le tocara, dos productores independientes -el razonamiento y el
55
+ * spinner llegan por caminos distintos- se turnarian el sitio y el pie bailaria.
56
+ */
57
+ const ORDEN = ["razonamiento", "spinner", "entrada"];
58
+ const secciones = new Map();
59
+ /**
60
+ * Texto permanente que aun no ha recibido su salto de linea.
61
+ *
62
+ * Un `flush()` de markdown puede dejar media linea escrita. Si el pie se
63
+ * pintara detras, empezaria en mitad de esa fila. En vez de calcular columnas
64
+ * -aritmetica que se rompe con cada emoji- se guarda ese resto AQUI y se
65
+ * repinta como primera linea del bloque. Deja de ser texto permanente hasta que
66
+ * llegue su salto, y entonces se escribe de verdad.
67
+ */
68
+ let colaSinSalto = "";
69
+ /** Filas fisicas que ocupo el bloque en el ultimo pintado. Es lo que se borra. */
70
+ let filasPintadas = 0;
71
+ /**
72
+ * Lo ultimo que escribimos NOSOTROS se quedo a medio renglon.
73
+ *
74
+ * Sin secciones puestas, la linea a medias deja de estar protegida y sale como
75
+ * texto permanente (ver `repintar`). Si despues vuelve a haber pie, tiene que
76
+ * empezar en un renglon nuevo y no pegado al final de esa frase.
77
+ */
78
+ let aMediaLinea = false;
79
+ let cursorEscondido = false;
80
+ /** `null` = decidirlo por el terminal. Las pruebas lo fuerzan. */
81
+ let habilitado = null;
82
+ function pieUsable() {
83
+ return habilitado ?? Boolean(process.stdout.isTTY);
84
+ }
85
+ /**
86
+ * Enciende o apaga el pie a mano. Con `null` vuelve a mandar el terminal.
87
+ *
88
+ * Las pruebas lo encienden porque en `node --test` no hay TTY y sin esto el pie
89
+ * seria un no-op silencioso: pasarian todas sin comprobar nada.
90
+ */
91
+ export function habilitarPie(valor) {
92
+ habilitado = valor;
93
+ }
94
+ function ancho() {
95
+ return Math.max(20, process.stdout.columns || 80);
96
+ }
97
+ function alto() {
98
+ return Math.max(4, process.stdout.rows || 24);
99
+ }
100
+ function sinColores(texto) {
101
+ return texto.replace(/\x1b\[[0-9;]*m/g, "");
102
+ }
103
+ /**
104
+ * Filas de pantalla que ocupa una linea con este ancho.
105
+ *
106
+ * `ceil(largo / cols)` coincide con el ajuste diferido de los terminales: tras
107
+ * escribir exactamente `cols` caracteres el cursor sigue en esa fila esperando,
108
+ * y eso es una fila, no dos.
109
+ */
110
+ function filasFisicas(linea, cols) {
111
+ return sinColores(linea)
112
+ .split("\n")
113
+ .reduce((n, seg) => n + Math.max(1, Math.ceil(seg.length / cols)), 0);
114
+ }
115
+ function lineasDelBloque() {
116
+ const lineas = [];
117
+ if (colaSinSalto)
118
+ lineas.push(colaSinSalto);
119
+ for (const nombre of ORDEN) {
120
+ const trozo = secciones.get(nombre);
121
+ if (trozo && trozo.length > 0)
122
+ lineas.push(...trozo);
123
+ }
124
+ return lineas;
125
+ }
126
+ /** Secuencia que deja el cursor donde empezaba el bloque, con todo borrado debajo. */
127
+ function borrado() {
128
+ if (filasPintadas === 0)
129
+ return "";
130
+ const subir = filasPintadas > 1 ? `\x1b[${filasPintadas - 1}A` : "";
131
+ return `\r${subir}\x1b[0J`;
132
+ }
133
+ /** Secuencia del bloque nuevo. Actualiza `filasPintadas`. */
134
+ function pintado() {
135
+ let lineas = lineasDelBloque();
136
+ if (lineas.length === 0) {
137
+ filasPintadas = 0;
138
+ return "";
139
+ }
140
+ const cols = ancho();
141
+ // Un pie mas alto que la ventana no se puede repintar: al pintarlo el
142
+ // terminal desplaza, y la cuenta de filas -que es lo unico que sabemos para
143
+ // borrarlo- deja de valer. Se recorta por abajo, que es donde esta lo menos
144
+ // urgente; la cola sin salto va primera y nunca se cae.
145
+ const tope = alto() - 1;
146
+ let filas = lineas.reduce((n, l) => n + filasFisicas(l, cols), 0);
147
+ while (filas > tope && lineas.length > 1) {
148
+ lineas = lineas.slice(0, -1);
149
+ filas = lineas.reduce((n, l) => n + filasFisicas(l, cols), 0);
150
+ }
151
+ filasPintadas = filas;
152
+ return lineas.join("\n");
153
+ }
154
+ /**
155
+ * El unico sitio que escribe. Borra lo de antes, suelta el texto permanente en
156
+ * medio, y vuelve a pintar el pie debajo. Todo en UNA escritura: un pie pintado
157
+ * a trozos es un pie que parpadea.
158
+ */
159
+ function repintar(permanente) {
160
+ const fuera = borrado();
161
+ // Sin secciones ya no hay de que proteger la linea a medias, y retenerla seria
162
+ // peor que soltarla: el pie seguiria contando esa fila como suya, y el
163
+ // siguiente repintado borraria lo que hubiera escrito cualquier otro por su
164
+ // cuenta -las herramientas imprimen con console.log y no pasan por aqui-.
165
+ // Soltandola, `filasPintadas` vuelve a cero y el pie no debe nada a nadie.
166
+ let suelto = "";
167
+ if (secciones.size === 0 && colaSinSalto) {
168
+ suelto = colaSinSalto;
169
+ colaSinSalto = "";
170
+ }
171
+ let dentro = pintado();
172
+ const texto = permanente + suelto;
173
+ if (texto)
174
+ aMediaLinea = !texto.endsWith("\n");
175
+ if (dentro && aMediaLinea) {
176
+ // El salto va DELANTE del bloque, y no cuenta como fila suya: el borrado
177
+ // sube hasta la primera fila del pie, no hasta la frase de encima.
178
+ dentro = `\n${dentro}`;
179
+ aMediaLinea = false;
180
+ }
181
+ let salida = fuera + texto + dentro;
182
+ if (dentro && !cursorEscondido) {
183
+ // Con el pie repintandose cada 120 ms, un cursor visible salta de sitio sin
184
+ // parar. Se esconde mientras dure y se devuelve al soltarlo.
185
+ salida = OCULTAR_CURSOR + salida;
186
+ cursorEscondido = true;
187
+ }
188
+ else if (!dentro && cursorEscondido) {
189
+ salida += MOSTRAR_CURSOR;
190
+ cursorEscondido = false;
191
+ }
192
+ if (salida)
193
+ dibujar(salida);
194
+ }
195
+ /** ¿Hay algo pintado abajo que haya que respetar? */
196
+ export function hayPie() {
197
+ return filasPintadas > 0;
198
+ }
199
+ function iguales(a, b) {
200
+ if (!a || a.length !== b.length)
201
+ return false;
202
+ return a.every((l, i) => l === b[i]);
203
+ }
204
+ /** Guarda la seccion. Devuelve si algo cambio, que es lo que decide repintar. */
205
+ function anotar(nombre, lineas) {
206
+ if (lineas.length === 0) {
207
+ return secciones.delete(nombre);
208
+ }
209
+ if (iguales(secciones.get(nombre), lineas))
210
+ return false;
211
+ secciones.set(nombre, lineas);
212
+ return true;
213
+ }
214
+ /**
215
+ * Pone (o reemplaza) una seccion del pie y repinta.
216
+ *
217
+ * Con la lista vacia la seccion desaparece, que es lo mismo que quitarla. Si el
218
+ * contenido es identico al que ya habia, no se repinta: el spinner llama ocho
219
+ * veces por segundo y la barra de escribir casi nunca cambia entre dos.
220
+ */
221
+ export function fijarSeccion(nombre, lineas) {
222
+ if (!pieUsable())
223
+ return;
224
+ if (anotar(nombre, lineas))
225
+ repintar("");
226
+ }
227
+ /**
228
+ * Varias secciones de una vez, con UN solo repintado.
229
+ *
230
+ * El spinner cambia su linea y la barra de escribir en el mismo tick. Puestas
231
+ * por separado se pintaba el bloque dos veces cada 120 ms, y la segunda pasada
232
+ * borraba y reescribia lo que la primera acababa de dejar: el doble de bytes
233
+ * para el mismo resultado, y un parpadeo que se nota en un terminal lento.
234
+ */
235
+ export function fijarSecciones(entradas) {
236
+ if (!pieUsable())
237
+ return;
238
+ let cambio = false;
239
+ for (const [nombre, lineas] of entradas) {
240
+ if (anotar(nombre, lineas))
241
+ cambio = true;
242
+ }
243
+ if (cambio)
244
+ repintar("");
245
+ }
246
+ export function quitarSeccion(nombre) {
247
+ fijarSeccion(nombre, []);
248
+ }
249
+ /**
250
+ * Escribe texto permanente sin que el pie se lo lleve por delante.
251
+ *
252
+ * Sin pie es un write normal y no cuesta nada; por eso puede sustituir a
253
+ * `console.log` en el camino del agente sin pensarlo dos veces.
254
+ */
255
+ export function escribirEncimaDelPie(texto) {
256
+ if (!texto)
257
+ return;
258
+ if (!pieUsable() || (filasPintadas === 0 && !colaSinSalto)) {
259
+ dibujar(texto);
260
+ return;
261
+ }
262
+ const completo = colaSinSalto + texto;
263
+ const corte = completo.lastIndexOf("\n");
264
+ const permanente = corte >= 0 ? completo.slice(0, corte + 1) : "";
265
+ colaSinSalto = corte >= 0 ? completo.slice(corte + 1) : completo;
266
+ repintar(permanente);
267
+ }
268
+ /**
269
+ * Quita el pie entero y deja el terminal como si nunca hubiera estado.
270
+ *
271
+ * Lo que quede a medias de linea se conserva en pantalla: es texto del agente,
272
+ * no adorno, y borrarlo seria perder salida.
273
+ */
274
+ export function soltarPie() {
275
+ if (secciones.size === 0 && filasPintadas === 0)
276
+ return;
277
+ secciones.clear();
278
+ repintar("");
279
+ }
280
+ /**
281
+ * Olvida el estado sin tocar la pantalla.
282
+ *
283
+ * Para las pruebas y para la salida del proceso: ahi no queda terminal al que
284
+ * repintar, pero el cursor escondido si sobrevive y hay que devolverlo.
285
+ */
286
+ export function reiniciarPie() {
287
+ secciones.clear();
288
+ colaSinSalto = "";
289
+ filasPintadas = 0;
290
+ aMediaLinea = false;
291
+ if (cursorEscondido) {
292
+ dibujar(MOSTRAR_CURSOR);
293
+ cursorEscondido = false;
294
+ }
295
+ }
296
+ /**
297
+ * Un cursor escondido sobrevive al proceso: la shell de despues se queda sin
298
+ * cursor y el usuario no tiene forma de saber por que. Se devuelve pase lo que
299
+ * pase, tambien si el programa se cae.
300
+ */
301
+ process.on("exit", () => {
302
+ if (cursorEscondido) {
303
+ try {
304
+ process.stdout.write(MOSTRAR_CURSOR);
305
+ }
306
+ catch { }
307
+ cursorEscondido = false;
308
+ }
309
+ });
package/dist/ui/prompt.js CHANGED
@@ -9,6 +9,7 @@ import { releaseKeyboard, tomarTecladoParaPrompt, devolverTecladoDelPrompt } fro
9
9
  import { repararTeclado, reafirmarModoCrudo, MS_ENTRE_COMPROBACIONES } from "./keyboardGuard.js";
10
10
  import { clearTyped } from "./typeAhead.js";
11
11
  import { dibujar } from "./salida.js";
12
+ import { escribirEncimaDelPie, reiniciarPie } from "./pieFijo.js";
12
13
  import { esGrande, guardarPegado, expandirPegados, marcaQueTerminaEn, limpiarPegados } from "./pegados.js";
13
14
  /**
14
15
  * Escribir algo mientras el prompt esta dibujado.
@@ -40,7 +41,9 @@ export function imprimirSobreElPrompt(texto) {
40
41
  escribirEncimaDelPrompt(texto);
41
42
  return;
42
43
  }
43
- dibujar(texto.endsWith("\n") ? texto : `${texto}\n`);
44
+ // Sin prompt abierto puede haber pie: el spinner y la linea de razonamiento
45
+ // viven ahi. Un aviso que llegue tarde tiene que pasar por encima igual.
46
+ escribirEncimaDelPie(texto.endsWith("\n") ? texto : `${texto}\n`);
44
47
  }
45
48
  export const SLASH_COMMANDS = [
46
49
  { name: "/mcp", description: "Ver servidores MCP conectados y sus herramientas" },
@@ -285,6 +288,11 @@ export function buildPromptFrame(opts) {
285
288
  }
286
289
  export async function askInteractivePrompt(promptSymbol = "❯", cwd = process.cwd()) {
287
290
  return new Promise((resolve) => {
291
+ // A partir de aqui el terminal es del prompt, que lleva su propia cuenta de
292
+ // filas. El pie fijo tiene que olvidar la suya SIN borrar nada: lo que dejo
293
+ // en pantalla es respuesta del agente, no adorno. Y devuelve el cursor, que
294
+ // lo escondia mientras se repintaba.
295
+ reiniciarPie();
288
296
  let inputBuffer = "";
289
297
  let cursorIndex = 0;
290
298
  let selectedIndex = 0;
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import process from "node:process";
3
+ import { fijarSeccion, quitarSeccion, escribirEncimaDelPie } from "./pieFijo.js";
3
4
  /**
4
5
  * Lo que se ve mientras el modelo piensa.
5
6
  *
@@ -23,9 +24,17 @@ import process from "node:process";
23
24
  * que aparecia justo en las tareas largas -las que mas razonan- y nunca al
24
25
  * saludar.
25
26
  *
26
- * Ahora se resume: una sola linea que se reescribe sobre si misma diciendo
27
- * cuanto lleva pensando y en que anda. Cero avance de scroll, una escritura por
28
- * trozo recibido en vez de una por letra.
27
+ * Ahora se resume: una sola linea que dice cuanto lleva pensando y en que anda.
28
+ * Cero avance de scroll, una escritura por trozo recibido en vez de una por
29
+ * letra.
30
+ *
31
+ * DONDE VIVE ESA LINEA
32
+ *
33
+ * En el pie fijo, no en la fila actual. Antes se reescribia sobre si misma con
34
+ * `\r\x1b[2K`, que la ata a la fila donde nacio: al empezar a llegar la
35
+ * respuesta, el terminal desplaza y se la lleva hacia arriba. Es la "nubecita
36
+ * que sube con lo que me respondes". Entregandola al pie, se repinta abajo tras
37
+ * cada linea de respuesta y se queda quieta.
29
38
  *
30
39
  * Quien quiera verlo entero: CHOCOLATITO_RAZONAMIENTO=completo.
31
40
  */
@@ -56,7 +65,7 @@ export class ReasoningStream {
56
65
  this.caracteres = 0;
57
66
  this.cola = "";
58
67
  if (COMPLETO) {
59
- process.stdout.write(`\n ${chalk.bold.hex("#8B5CF6")("💭 Razonando:")}\n`);
68
+ escribirEncimaDelPie(`\n ${chalk.bold.hex("#8B5CF6")("💭 Razonando:")}\n`);
60
69
  }
61
70
  }
62
71
  if (COMPLETO) {
@@ -87,22 +96,20 @@ export class ReasoningStream {
87
96
  }
88
97
  // Un solo write, y el color se aplica una vez al bloque entero en vez de a
89
98
  // cada letra: mismo resultado en pantalla, una fraccion de los bytes.
90
- process.stdout.write(chalk.gray(salida));
99
+ escribirEncimaDelPie(chalk.gray(salida));
91
100
  }
92
101
  refrescar(forzar) {
93
102
  const ahora = Date.now();
94
103
  if (!forzar && ahora - this.ultimoRefresco < MS_ENTRE_REFRESCOS)
95
104
  return;
96
105
  this.ultimoRefresco = ahora;
97
- if (!process.stdout.isTTY)
98
- return;
99
106
  const segundos = Math.round((ahora - this.inicio) / 1000);
100
107
  const ancho = Math.max(20, (process.stdout.columns || 80) - 4);
101
108
  const cabeza = ` ${chalk.hex("#8B5CF6")("💭")} ${chalk.gray(`pensando… ${segundos}s`)}`;
102
109
  // La ultima frase a medias da una pista de por donde va sin contarlo todo.
103
110
  const pista = unaLinea(this.cola, Math.max(0, ancho - 24));
104
111
  const linea = pista ? `${cabeza} ${chalk.dim("·")} ${chalk.dim(pista)}` : cabeza;
105
- process.stdout.write(`\r\x1b[2K${unaLinea(linea, ancho + 40)}`);
112
+ fijarSeccion("razonamiento", [linea]);
106
113
  this.lineaPintada = true;
107
114
  }
108
115
  flush() {
@@ -110,17 +117,20 @@ export class ReasoningStream {
110
117
  return;
111
118
  if (COMPLETO) {
112
119
  if (!this.atLineStart) {
113
- process.stdout.write("\n");
120
+ escribirEncimaDelPie("\n");
114
121
  this.atLineStart = true;
115
122
  }
116
- process.stdout.write("\n");
123
+ escribirEncimaDelPie("\n");
117
124
  this.hasStarted = false;
118
125
  return;
119
126
  }
120
127
  const segundos = Math.max(1, Math.round((Date.now() - this.inicio) / 1000));
128
+ // Primero se quita la linea viva del pie y despues se escribe el resumen
129
+ // como texto permanente. Al reves, el resumen quedaria por encima de una
130
+ // linea de "pensando…" que ya no viene a cuento.
131
+ quitarSeccion("razonamiento");
121
132
  if (this.lineaPintada) {
122
- // Se deja una sola linea de resumen donde estaba la que iba cambiando.
123
- process.stdout.write(`\r\x1b[2K ${chalk.hex("#8B5CF6")("💭")} ${chalk.gray(`pensó ${segundos}s`)}\n`);
133
+ escribirEncimaDelPie(` ${chalk.hex("#8B5CF6")("💭")} ${chalk.gray(`pensó ${segundos}s`)}\n`);
124
134
  }
125
135
  this.lineaPintada = false;
126
136
  this.hasStarted = false;
@@ -6,19 +6,26 @@ export declare class DynamicSpinner {
6
6
  private isRunning;
7
7
  private prefix;
8
8
  tokenCount: number;
9
- /** Lineas extra pintadas debajo del spinner en el ultimo tick. */
10
- private extraRows;
11
9
  constructor(prefix?: string);
12
10
  start(initialPrefix?: string): void;
13
11
  private tick;
14
12
  /**
15
- * Segunda linea con lo que el usuario esta escribiendo mientras se genera.
13
+ * `✳ Compactando historial… (3s · 1.2k tokens · esc para pausar) ⇥ modo`
16
14
  *
17
- * Antes el teclado se tragaba todo sin pintar nada y parecia que el terminal
18
- * se habia colgado. Se dibuja debajo y el cursor vuelve arriba, para que el
19
- * siguiente tick sobrescriba las dos lineas sin dejar rastro.
15
+ * El prefijo se pasaba en `start("Compactando historial")` y no se pintaba en
16
+ * ningun sitio: `tick()` usaba siempre los verbos rotatorios. Asi que las
17
+ * cuatro esperas con nombre propio -compactar, optimizar memoria, verificar-
18
+ * salian todas como "Rumiando…", indistinguibles de una peticion normal.
20
19
  */
21
- private drawTypeAhead;
22
- private clearExtraRows;
20
+ private lineaDelSpinner;
21
+ /**
22
+ * La barra de escribir, debajo del spinner.
23
+ *
24
+ * Antes solo aparecia si ya habias escrito algo, asi que mientras el agente
25
+ * trabajaba no habia ninguna senal de que se pudiera escribir: el teclado es
26
+ * del vigilante y lo tecleado se acumulaba a ciegas. Ahora la barra esta
27
+ * siempre, y es la misma que se ve al escribir el prompt.
28
+ */
29
+ private lineasDeEntrada;
23
30
  stop(): void;
24
31
  }