chocolatito-code 1.1.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.
Files changed (55) hide show
  1. package/README.md +66 -0
  2. package/dist/agent/huecos.d.ts +58 -0
  3. package/dist/agent/huecos.js +81 -0
  4. package/dist/agent/loop.js +218 -41
  5. package/dist/agent/salidaDelAgente.d.ts +43 -0
  6. package/dist/agent/salidaDelAgente.js +39 -0
  7. package/dist/agent/toolGate.js +6 -1
  8. package/dist/config/engine.d.ts +21 -1
  9. package/dist/config/engine.js +22 -1
  10. package/dist/config/nodeVersion.d.ts +13 -0
  11. package/dist/config/nodeVersion.js +26 -0
  12. package/dist/index.js +22 -7
  13. package/dist/mcp/client.d.ts +28 -0
  14. package/dist/mcp/client.js +62 -8
  15. package/dist/mcp/ide.d.ts +76 -0
  16. package/dist/mcp/ide.js +160 -0
  17. package/dist/mcp/manager.js +9 -0
  18. package/dist/tools/browserExtension.js +153 -7
  19. package/dist/tools/computerUse.d.ts +1 -1
  20. package/dist/tools/computerUse.js +183 -2
  21. package/dist/tools/definitions.js +9 -2
  22. package/dist/tools/grepSearch.js +111 -23
  23. package/dist/tools/runner.js +1 -1
  24. package/dist/tools/toolDefsComputer.js +6 -2
  25. package/dist/tools/visionBridge.js +60 -3
  26. package/dist/tools/win/hostScript.js +154 -0
  27. package/dist/tools/writeFile.d.ts +16 -1
  28. package/dist/tools/writeFile.js +22 -1
  29. package/dist/ui/interrupt.d.ts +10 -0
  30. package/dist/ui/interrupt.js +53 -4
  31. package/dist/ui/keyboardGuard.d.ts +21 -0
  32. package/dist/ui/keyboardGuard.js +30 -1
  33. package/dist/ui/pegados.d.ts +50 -0
  34. package/dist/ui/pegados.js +83 -0
  35. package/dist/ui/pieFijo.d.ts +56 -0
  36. package/dist/ui/pieFijo.js +309 -0
  37. package/dist/ui/prompt.d.ts +5 -0
  38. package/dist/ui/prompt.js +109 -16
  39. package/dist/ui/reasoningStream.js +22 -12
  40. package/dist/ui/salida.d.ts +10 -0
  41. package/dist/ui/salida.js +50 -0
  42. package/dist/ui/spinner.d.ts +15 -8
  43. package/dist/ui/spinner.js +80 -42
  44. package/extension/background.js +191 -14
  45. package/extension/content.js +446 -48
  46. package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
  47. package/extension/iconos/128.png +0 -0
  48. package/extension/iconos/16.png +0 -0
  49. package/extension/iconos/32.png +0 -0
  50. package/extension/iconos/48.png +0 -0
  51. package/extension/iconos/ORIGEN.md +48 -0
  52. package/extension/iconos/logotipo.png +0 -0
  53. package/extension/manifest.json +27 -4
  54. package/extension/popup.html +16 -6
  55. package/package.json +3 -2
@@ -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;
@@ -0,0 +1,10 @@
1
+ /** Pinta encima del terminal. En produccion es `process.stdout.write` y nada mas. */
2
+ export declare function dibujar(texto: string): void;
3
+ /**
4
+ * Desvia el dibujo. Con `null` vuelve al terminal.
5
+ *
6
+ * Solo lo usan las pruebas, y siempre devolviendolo en un `finally`: si se
7
+ * queda desviado, las pruebas que vengan detras dibujan en el vacio sin
8
+ * enterarse.
9
+ */
10
+ export declare function redirigirDibujo(destino: ((texto: string) => void) | null): void;
@@ -0,0 +1,50 @@
1
+ import process from "node:process";
2
+ /**
3
+ * Por donde sale lo que se pinta encima del terminal.
4
+ *
5
+ * Aqui va SOLO el dibujo que ocupa el terminal a la fuerza: el marco del
6
+ * prompt, los avisos que se escriben pisando la linea actual con \r y \x1b[2K.
7
+ * Un `console.log` normal no pasa por aqui y no tiene por que.
8
+ *
9
+ * POR QUE EXISTE
10
+ *
11
+ * `npm publish` corre `prepublishOnly` -> `npm test`, y las pruebas del teclado
12
+ * abren el prompt de verdad. Con un terminal delante, el prompt mueve el
13
+ * cursor, borra lineas y repinta su marco encima de la sesion de quien esta
14
+ * publicando. En un `npm publish` real se vio esto:
15
+ *
16
+ * ❯ hola mundo
17
+ * ↳ en cola: y ahora los tests
18
+ *
19
+ * El terminal dejaba de ser suyo y la publicacion no arrancaba. Hubo que
20
+ * publicar con --ignore-scripts, saltandose las 488 pruebas; una red de
21
+ * seguridad que hay que apagar para trabajar no es una red.
22
+ *
23
+ * POR QUE NO SE SILENCIA `process.stdout` DESDE FUERA
24
+ *
25
+ * Se intento de las dos formas y las dos rompen algo peor. `node --test`
26
+ * instrumenta process.stdout para asociar cada linea a la prueba que la
27
+ * produjo: al sustituir el objeto -o solo su `write`- las pruebas se ejecutan
28
+ * pero desaparecen del informe, sin fallar y sin dejar rastro. Medido: de 6
29
+ * pruebas pasaban a contarse 2, en verde. Una prueba que se evapora es peor que
30
+ * una que falla.
31
+ *
32
+ * Con un conducto propio, process.stdout no se toca nunca.
33
+ */
34
+ let conducto = (texto) => {
35
+ process.stdout.write(texto);
36
+ };
37
+ /** Pinta encima del terminal. En produccion es `process.stdout.write` y nada mas. */
38
+ export function dibujar(texto) {
39
+ conducto(texto);
40
+ }
41
+ /**
42
+ * Desvia el dibujo. Con `null` vuelve al terminal.
43
+ *
44
+ * Solo lo usan las pruebas, y siempre devolviendolo en un `finally`: si se
45
+ * queda desviado, las pruebas que vengan detras dibujan en el vacio sin
46
+ * enterarse.
47
+ */
48
+ export function redirigirDibujo(destino) {
49
+ conducto = destino ?? ((texto) => { process.stdout.write(texto); });
50
+ }
@@ -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
  }
@@ -2,6 +2,22 @@ import chalk from "chalk";
2
2
  import process from "node:process";
3
3
  import { typedText, queuedCount } from "./typeAhead.js";
4
4
  import { modeBadge } from "./modes.js";
5
+ import { fijarSecciones } from "./pieFijo.js";
6
+ /**
7
+ * LA LINEA QUE DICE QUE ESTO SIGUE VIVO
8
+ *
9
+ * Antes se pintaba con `\r\x1b[2K` sobre la fila donde estuviera el cursor. Eso
10
+ * la deja clavada a esa fila, y en cuanto la respuesta empieza a salir el
11
+ * terminal desplaza el contenido y se lleva el spinner hacia arriba con el.
12
+ * Palabras del usuario: "la nubecita y el rayito suben con lo que me respondes".
13
+ *
14
+ * Ahora se entrega a `pieFijo`, que la vuelve a pintar abajo despues de cada
15
+ * escritura. La linea deja de moverse aunque salgan cien lineas de respuesta.
16
+ *
17
+ * Y por eso mismo ya no hace falta pararlo al llegar el primer trozo: el
18
+ * contador de segundos y de tokens sigue a la vista MIENTRAS el modelo habla,
19
+ * que es justo cuando interesa saber cuanto lleva.
20
+ */
5
21
  const GLYPHS = ["✢", "✳", "∗", "✻", "✽"];
6
22
  const VERBS = [
7
23
  "Analizando",
@@ -15,6 +31,18 @@ const VERBS = [
15
31
  "Tejiendo",
16
32
  "Cocinando",
17
33
  ];
34
+ const PISTA_VACIA = "escribe para encolar un mensaje…";
35
+ function sinColores(texto) {
36
+ return texto.replace(/\x1b\[[0-9;]*m/g, "");
37
+ }
38
+ /** Deja las ULTIMAS `ancho` columnas: al escribir, lo que importa es el final. */
39
+ function colaVisible(texto, ancho) {
40
+ if (ancho <= 0)
41
+ return "";
42
+ if (texto.length <= ancho)
43
+ return texto;
44
+ return `…${texto.slice(-(ancho - 1))}`;
45
+ }
18
46
  export class DynamicSpinner {
19
47
  timer = null;
20
48
  glyphIdx = 0;
@@ -23,8 +51,6 @@ export class DynamicSpinner {
23
51
  isRunning = false;
24
52
  prefix = "";
25
53
  tokenCount = 0;
26
- /** Lineas extra pintadas debajo del spinner en el ultimo tick. */
27
- extraRows = 0;
28
54
  constructor(prefix = "") {
29
55
  this.prefix = prefix;
30
56
  }
@@ -37,7 +63,7 @@ export class DynamicSpinner {
37
63
  this.startTime = Date.now();
38
64
  this.glyphIdx = 0;
39
65
  this.verbIdx = 0;
40
- this.extraRows = 0;
66
+ this.tick();
41
67
  this.timer = setInterval(() => {
42
68
  this.tick();
43
69
  }, 120);
@@ -47,55 +73,63 @@ export class DynamicSpinner {
47
73
  return;
48
74
  this.glyphIdx = (this.glyphIdx + 1) % GLYPHS.length;
49
75
  const elapsedSec = Math.floor((Date.now() - this.startTime) / 1000);
50
- // Rotate verbs every 2.5 seconds
51
76
  this.verbIdx = Math.floor(elapsedSec / 2.5) % VERBS.length;
77
+ // Las dos juntas: por separado se repinta el bloque dos veces por tick.
78
+ fijarSecciones([
79
+ ["spinner", [this.lineaDelSpinner(elapsedSec)]],
80
+ ["entrada", this.lineasDeEntrada()],
81
+ ]);
82
+ }
83
+ /**
84
+ * `✳ Compactando historial… (3s · ↓ 1.2k tokens · esc para pausar) ⇥ modo`
85
+ *
86
+ * El prefijo se pasaba en `start("Compactando historial")` y no se pintaba en
87
+ * ningun sitio: `tick()` usaba siempre los verbos rotatorios. Asi que las
88
+ * cuatro esperas con nombre propio -compactar, optimizar memoria, verificar-
89
+ * salian todas como "Rumiando…", indistinguibles de una peticion normal.
90
+ */
91
+ lineaDelSpinner(elapsedSec) {
92
+ const cols = Math.max(20, process.stdout.columns || 80);
52
93
  const glyph = chalk.hex("#D97757")(GLYPHS[this.glyphIdx]);
53
- const verb = chalk.italic.hex("#D97757")(`${VERBS[this.verbIdx]}…`);
54
- const timeStr = chalk.gray(`(${elapsedSec}s`);
55
- const tokenStr = this.tokenCount > 0 ? chalk.gray(` · ${(this.tokenCount / 1000).toFixed(1)}k tokens`) : "";
56
- const escStr = chalk.gray(` · esc para pausar)`);
94
+ const verbo = this.prefix || VERBS[this.verbIdx];
95
+ const verb = chalk.italic.hex("#D97757")(`${verbo}…`);
96
+ // El contador va hacia abajo: son los tokens que ESTAN LLEGANDO del modelo.
97
+ const tokenStr = this.tokenCount > 0 ? ` · ${(this.tokenCount / 1000).toFixed(1)}k tokens` : "";
98
+ const cola = chalk.gray(`(${elapsedSec}s${tokenStr} · esc para pausar)`);
99
+ const izquierda = `${glyph} ${verb} ${cola}`;
100
+ const anchoIzq = 1 + 1 + verbo.length + 1 + 1 + `(${elapsedSec}s${tokenStr} · esc para pausar)`.length;
101
+ // La chapa del modo se pega a la derecha, y solo si sobra sitio: antes se
102
+ // concatenaba siempre y en una consola estrecha empujaba la linea a una
103
+ // segunda fila, que es justo lo que descuadra el repintado.
57
104
  const badge = modeBadge();
58
- const modeStr = badge ? ` ${badge}` : "";
59
- this.clearExtraRows();
60
- process.stdout.write(`\r\x1b[2K${glyph} ${verb} ${timeStr}${tokenStr}${escStr}${modeStr}`);
61
- this.drawTypeAhead();
105
+ if (!badge)
106
+ return izquierda;
107
+ const anchoBadge = sinColores(badge).length;
108
+ const hueco = cols - anchoIzq - anchoBadge;
109
+ return hueco >= 2 ? `${izquierda}${" ".repeat(hueco)}${badge}` : izquierda;
62
110
  }
63
111
  /**
64
- * Segunda linea con lo que el usuario esta escribiendo mientras se genera.
112
+ * La barra de escribir, debajo del spinner.
65
113
  *
66
- * Antes el teclado se tragaba todo sin pintar nada y parecia que el terminal
67
- * se habia colgado. Se dibuja debajo y el cursor vuelve arriba, para que el
68
- * siguiente tick sobrescriba las dos lineas sin dejar rastro.
114
+ * Antes solo aparecia si ya habias escrito algo, asi que mientras el agente
115
+ * trabajaba no habia ninguna senal de que se pudiera escribir: el teclado es
116
+ * del vigilante y lo tecleado se acumulaba a ciegas. Ahora la barra esta
117
+ * siempre, y es la misma que se ve al escribir el prompt.
69
118
  */
70
- drawTypeAhead() {
119
+ lineasDeEntrada() {
120
+ const cols = Math.max(20, process.stdout.columns || 80);
71
121
  const typing = typedText();
72
122
  const pendientes = queuedCount();
73
- const rows = [];
74
- if (typing.length > 0) {
75
- rows.push(`${chalk.hex("#D97757")("❯")} ${chalk.white(typing)}${chalk.gray(" (enter para encolarlo)")}`);
76
- }
123
+ const simbolo = chalk.bold.hex("#D97757")("❯");
124
+ const cuerpo = typing.length > 0
125
+ ? `${chalk.white(colaVisible(typing, cols - 3))}${chalk.hex("#D97757")("▌")}`
126
+ : chalk.gray(colaVisible(PISTA_VACIA, cols - 2));
127
+ const filas = [chalk.gray("─".repeat(cols)), `${simbolo} ${cuerpo}`];
77
128
  if (pendientes > 0) {
78
129
  const plural = pendientes === 1 ? "mensaje en cola" : "mensajes en cola";
79
- rows.push(chalk.gray(` ${pendientes} ${plural}; se enviará al terminar este turno`));
80
- }
81
- if (rows.length === 0) {
82
- this.extraRows = 0;
83
- return;
84
- }
85
- for (const row of rows) {
86
- process.stdout.write(`\n\r\x1b[2K${row}`);
87
- }
88
- process.stdout.write(`\x1b[${rows.length}A\r`);
89
- this.extraRows = rows.length;
90
- }
91
- clearExtraRows() {
92
- if (this.extraRows === 0)
93
- return;
94
- for (let i = 0; i < this.extraRows; i++) {
95
- process.stdout.write("\x1b[1B\r\x1b[2K");
130
+ filas.push(chalk.gray(` ${pendientes} ${plural}; se enviará al terminar este turno`));
96
131
  }
97
- process.stdout.write(`\x1b[${this.extraRows}A\r`);
98
- this.extraRows = 0;
132
+ return filas;
99
133
  }
100
134
  stop() {
101
135
  if (!this.isRunning)
@@ -105,7 +139,11 @@ export class DynamicSpinner {
105
139
  clearInterval(this.timer);
106
140
  this.timer = null;
107
141
  }
108
- this.clearExtraRows();
109
- process.stdout.write("\r\x1b[2K");
142
+ // Las dos de golpe: quitarlas por separado deja un fotograma con la barra
143
+ // de escribir suelta y sin spinner encima.
144
+ fijarSecciones([
145
+ ["spinner", []],
146
+ ["entrada", []],
147
+ ]);
110
148
  }
111
149
  }