chocolatito-code 1.0.0 → 1.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.
Files changed (102) hide show
  1. package/README.md +89 -0
  2. package/dist/agent/compaction.d.ts +65 -0
  3. package/dist/agent/compaction.js +134 -0
  4. package/dist/agent/context.d.ts +3 -0
  5. package/dist/agent/context.js +35 -0
  6. package/dist/agent/loop.d.ts +91 -2
  7. package/dist/agent/loop.js +536 -78
  8. package/dist/agent/loopDetector.d.ts +57 -0
  9. package/dist/agent/loopDetector.js +0 -0
  10. package/dist/agent/mentions.d.ts +13 -0
  11. package/dist/agent/mentions.js +49 -0
  12. package/dist/agent/repoMap.d.ts +117 -0
  13. package/dist/agent/repoMap.js +560 -0
  14. package/dist/agent/result.d.ts +64 -0
  15. package/dist/agent/result.js +48 -0
  16. package/dist/agent/retry.d.ts +35 -0
  17. package/dist/agent/retry.js +123 -4
  18. package/dist/agent/subagent.js +62 -15
  19. package/dist/agent/syntaxValidator.js +288 -52
  20. package/dist/agent/toolGate.d.ts +58 -0
  21. package/dist/agent/toolGate.js +111 -0
  22. package/dist/agent/tracker.js +4 -7
  23. package/dist/agent/undoManager.d.ts +44 -11
  24. package/dist/agent/undoManager.js +95 -24
  25. package/dist/agent/usageMeter.d.ts +56 -0
  26. package/dist/agent/usageMeter.js +51 -0
  27. package/dist/agent/verifier.d.ts +49 -0
  28. package/dist/agent/verifier.js +158 -0
  29. package/dist/config/constants.js +14 -0
  30. package/dist/config/engine.d.ts +19 -0
  31. package/dist/config/engine.js +35 -3
  32. package/dist/config/license.d.ts +5 -0
  33. package/dist/config/license.js +107 -9
  34. package/dist/config/limits.d.ts +28 -0
  35. package/dist/config/limits.js +51 -0
  36. package/dist/config/permissions.d.ts +76 -1
  37. package/dist/config/permissions.js +379 -23
  38. package/dist/config/quota.js +24 -1
  39. package/dist/config/updater.d.ts +68 -0
  40. package/dist/config/updater.js +215 -0
  41. package/dist/hooks/manager.js +159 -14
  42. package/dist/index.js +222 -34
  43. package/dist/mcp/client.d.ts +22 -0
  44. package/dist/mcp/client.js +93 -11
  45. package/dist/prompts/systemPrompt.js +12 -1
  46. package/dist/sessions/manager.d.ts +12 -0
  47. package/dist/sessions/manager.js +33 -5
  48. package/dist/sessions/resume.d.ts +36 -0
  49. package/dist/sessions/resume.js +43 -0
  50. package/dist/skills/manager.d.ts +1 -0
  51. package/dist/skills/manager.js +9 -0
  52. package/dist/tools/binary.d.ts +46 -0
  53. package/dist/tools/binary.js +100 -0
  54. package/dist/tools/browserCdp.js +13 -1
  55. package/dist/tools/browserExtension.d.ts +5 -0
  56. package/dist/tools/browserExtension.js +202 -18
  57. package/dist/tools/browserSession.d.ts +23 -0
  58. package/dist/tools/browserSession.js +28 -0
  59. package/dist/tools/computerUse.js +33 -1
  60. package/dist/tools/createImage.d.ts +11 -0
  61. package/dist/tools/createImage.js +13 -1
  62. package/dist/tools/definitions.js +9 -3
  63. package/dist/tools/editDiagnosis.d.ts +54 -0
  64. package/dist/tools/editDiagnosis.js +142 -0
  65. package/dist/tools/editFile.d.ts +1 -0
  66. package/dist/tools/editFile.js +118 -62
  67. package/dist/tools/findFiles.d.ts +21 -0
  68. package/dist/tools/findFiles.js +75 -6
  69. package/dist/tools/getSystemInfo.js +15 -7
  70. package/dist/tools/gitTools.d.ts +24 -0
  71. package/dist/tools/gitTools.js +87 -26
  72. package/dist/tools/moveCopyFile.d.ts +18 -1
  73. package/dist/tools/moveCopyFile.js +74 -4
  74. package/dist/tools/patchCascade.d.ts +57 -0
  75. package/dist/tools/patchCascade.js +338 -0
  76. package/dist/tools/runCommand.js +213 -25
  77. package/dist/tools/runner.js +57 -4
  78. package/dist/tools/toolDefsComputer.js +12 -2
  79. package/dist/tools/truncator.d.ts +27 -1
  80. package/dist/tools/truncator.js +117 -11
  81. package/dist/tools/viewFile.js +15 -11
  82. package/dist/tools/visionBridge.js +7 -0
  83. package/dist/tools/webSearch.d.ts +21 -0
  84. package/dist/tools/webSearch.js +125 -14
  85. package/dist/tools/writeFile.js +18 -1
  86. package/dist/ui/interrupt.d.ts +4 -1
  87. package/dist/ui/interrupt.js +56 -5
  88. package/dist/ui/keyboardGuard.d.ts +55 -0
  89. package/dist/ui/keyboardGuard.js +65 -0
  90. package/dist/ui/loopPrompt.d.ts +17 -0
  91. package/dist/ui/loopPrompt.js +59 -0
  92. package/dist/ui/permissionPrompt.js +84 -2
  93. package/dist/ui/prompt.d.ts +35 -0
  94. package/dist/ui/prompt.js +345 -125
  95. package/dist/ui/reasoningStream.d.ts +8 -0
  96. package/dist/ui/reasoningStream.js +99 -7
  97. package/extension/README.md +95 -74
  98. package/extension/background.js +598 -178
  99. package/extension/content.js +92 -9
  100. package/extension/manifest.json +2 -1
  101. package/extension/popup.js +28 -12
  102. package/package.json +1 -1
@@ -32,6 +32,42 @@ import { pushTyped, backspaceTyped, commitTyped } from "./typeAhead.js";
32
32
  */
33
33
  const activos = new Set();
34
34
  /** Suelta el teclado de cualquier vigilante que lo tenga. Lo llama el prompt. */
35
+ /**
36
+ * Como estaba el terminal ANTES de que ningun vigilante lo tocara.
37
+ *
38
+ * Cada vigilante se guardaba su propio `wasRaw` leyendo el estado en el momento
39
+ * de arrancar. Con un vigilante suelto -y el bucle tiene veinte salidas, basta
40
+ * que una se escape- el siguiente leia el modo crudo que habia dejado el
41
+ * anterior y se lo apuntaba como "lo de antes". Al soltarlos todos, cada uno
42
+ * restauraba su version y ganaba el ultimo: el terminal acababa en un estado que
43
+ * no era el de nadie.
44
+ *
45
+ * Con una sola marca, tomada por el primero y devuelta por el ultimo, el numero
46
+ * de vigilantes deja de importar.
47
+ */
48
+ let modoCrudoOriginal = null;
49
+ /**
50
+ * Hay un prompt dibujado ahora mismo.
51
+ *
52
+ * Un vigilante puede pararse TARDE: el bucle vuelve, el prompt se dibuja y coge
53
+ * el modo crudo, y solo entonces una promesa rezagada del turno anterior llama a
54
+ * su `stop()`. Ese stop restauraba "como estaba antes del turno" —modo cocido—
55
+ * y se lo quitaba al prompt recien abierto. Desde fuera se veia como el fallo
56
+ * que se reporto tres veces: tecleas y tu texto sale en la linea de ayuda, no
57
+ * en el recuadro, porque quien hace el eco es el terminal.
58
+ *
59
+ * Con esta marca, el ultimo vigilante en irse solo devuelve el terminal si no
60
+ * hay nadie esperandolo.
61
+ */
62
+ let promptConElTeclado = false;
63
+ /** Lo llama el prompt al abrirse, despues de coger el modo crudo. */
64
+ export function tomarTecladoParaPrompt() {
65
+ promptConElTeclado = true;
66
+ }
67
+ /** Lo llama el prompt al cerrarse, para que el siguiente turno pueda restaurar. */
68
+ export function devolverTecladoDelPrompt() {
69
+ promptConElTeclado = false;
70
+ }
35
71
  export function releaseKeyboard() {
36
72
  for (const w of Array.from(activos)) {
37
73
  w.stop();
@@ -62,7 +98,11 @@ export class InterruptWatcher {
62
98
  return this.controller.signal;
63
99
  }
64
100
  readline.emitKeypressEvents(process.stdin);
65
- this.wasRaw = process.stdin.isRaw || false;
101
+ // Solo el primero apunta como estaba: los que vengan detras verian el modo
102
+ // crudo que dejo este, no el del usuario.
103
+ if (activos.size === 0)
104
+ modoCrudoOriginal = process.stdin.isRaw || false;
105
+ this.wasRaw = modoCrudoOriginal ?? false;
66
106
  process.stdin.setRawMode(true);
67
107
  process.stdin.resume();
68
108
  this.onKey = (str, key) => {
@@ -137,12 +177,23 @@ export class InterruptWatcher {
137
177
  process.stdin.removeListener("keypress", this.onKey);
138
178
  this.onKey = null;
139
179
  }
140
- if (this.listening && process.stdin.setRawMode) {
141
- // Se devuelve el terminal como estaba, o el prompt siguiente queda roto.
142
- process.stdin.setRawMode(this.wasRaw);
143
- }
180
+ const escuchaba = this.listening;
144
181
  this.listening = false;
145
182
  activos.delete(this);
183
+ // El terminal se devuelve cuando se va el ULTIMO, no cada vez que se va
184
+ // uno: si quedan vigilantes vivos, restaurar aqui les quita el teclado a
185
+ // ellos a mitad de turno.
186
+ if (escuchaba && activos.size === 0) {
187
+ if (promptConElTeclado) {
188
+ // Llegamos tarde: el prompt ya esta abierto y el modo crudo es suyo.
189
+ // Solo se olvida la marca, que ya no describe nada.
190
+ modoCrudoOriginal = null;
191
+ }
192
+ else if (process.stdin.setRawMode) {
193
+ process.stdin.setRawMode(modoCrudoOriginal ?? this.wasRaw);
194
+ modoCrudoOriginal = null;
195
+ }
196
+ }
146
197
  }
147
198
  }
148
199
  /** Un error de aborto no es un fallo: es el usuario diciendo "para". */
@@ -0,0 +1,55 @@
1
+ /**
2
+ * El prompt no suelta el teclado mientras esta abierto.
3
+ *
4
+ * EL FALLO QUE CIERRA ESTE ARCHIVO
5
+ *
6
+ * Reportado tres veces desde el uso real, siempre igual: "escribi 2 prompts y
7
+ * al siguiente ya no me deja escribir nada, teclee lo que teclee". La sesion
8
+ * pegada por el usuario tenia la pista buena, escondida en la ultima linea:
9
+ *
10
+ * ? /help para atajos · esc cancelar ⇥ modo normal · Espectro 1.0 (Agil) estaba haciendo y denuevo...
11
+ *
12
+ * Lo que escribia SI aparecia, pero pegado al final de la linea de ayuda en vez
13
+ * de dentro del recuadro. Eso solo pasa de una forma: el modo crudo estaba
14
+ * apagado, asi que quien hacia el eco era PowerShell —en el sitio donde estuviera
15
+ * el cursor— y el prompt no se enteraba de ninguna tecla, porque en modo cocido
16
+ * no llega nada hasta que se pulsa Enter. El teclado no estaba muerto: estaba
17
+ * en manos del terminal.
18
+ *
19
+ * POR QUE NO SE ARREGLA EN EL SITIO QUE LO ROMPE
20
+ *
21
+ * Hay demasiados sitios que lo rompen, y todos con razon:
22
+ *
23
+ * - `InterruptWatcher.stop()` devuelve el modo crudo a como estaba. Si un
24
+ * vigilante se para TARDE —despues de que el prompt ya se haya dibujado—
25
+ * restaura un estado que ya no toca y se lo quita al prompt.
26
+ * - `readline.createInterface(...).close()` hace por dentro `pause()` y
27
+ * `setRawMode(false)`. Lo usan el dialogo de "¿que prefieres que haga?" y la
28
+ * pregunta del detector de bucles.
29
+ * - Cualquier salida del bucle que se escape sin parar lo suyo.
30
+ *
31
+ * Ya hubo dos intentos de arreglarlo apretando tuercas una por una, y el fallo
32
+ * volvio las dos veces, porque basta que UNA de las veinte salidas se escape.
33
+ * Asi que el prompt deja de confiar: mientras esta abierto, comprueba cuatro
34
+ * veces por segundo que el teclado sigue siendo suyo y lo recupera si no.
35
+ *
36
+ * Cuesta cuatro comparaciones cada 250 ms. El fallo costaba cerrar el terminal.
37
+ */
38
+ /** Lo minimo de `process.stdin` que hace falta, para poder probarlo con uno falso. */
39
+ export interface EntradaDeTeclado {
40
+ isTTY?: boolean;
41
+ isRaw?: boolean;
42
+ setRawMode?: (flag: boolean) => unknown;
43
+ isPaused?: () => boolean;
44
+ resume?: () => unknown;
45
+ }
46
+ /**
47
+ * Devuelve el teclado al prompt si alguien se lo ha quitado.
48
+ *
49
+ * Devuelve la lista de lo que ha tenido que reparar. Vacia es lo normal: si
50
+ * alguna vez devuelve algo, hay un fallo de verdad en quien solto el teclado, y
51
+ * por eso `CHOCOLATITO_TECLADO=debug` lo saca por pantalla en vez de callarselo.
52
+ */
53
+ export declare function repararTeclado(entrada: EntradaDeTeclado): string[];
54
+ /** Cada cuanto se comprueba. Cuatro veces por segundo se nota como instantaneo. */
55
+ export declare const MS_ENTRE_COMPROBACIONES = 250;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * El prompt no suelta el teclado mientras esta abierto.
3
+ *
4
+ * EL FALLO QUE CIERRA ESTE ARCHIVO
5
+ *
6
+ * Reportado tres veces desde el uso real, siempre igual: "escribi 2 prompts y
7
+ * al siguiente ya no me deja escribir nada, teclee lo que teclee". La sesion
8
+ * pegada por el usuario tenia la pista buena, escondida en la ultima linea:
9
+ *
10
+ * ? /help para atajos · esc cancelar ⇥ modo normal · Espectro 1.0 (Agil) estaba haciendo y denuevo...
11
+ *
12
+ * Lo que escribia SI aparecia, pero pegado al final de la linea de ayuda en vez
13
+ * de dentro del recuadro. Eso solo pasa de una forma: el modo crudo estaba
14
+ * apagado, asi que quien hacia el eco era PowerShell —en el sitio donde estuviera
15
+ * el cursor— y el prompt no se enteraba de ninguna tecla, porque en modo cocido
16
+ * no llega nada hasta que se pulsa Enter. El teclado no estaba muerto: estaba
17
+ * en manos del terminal.
18
+ *
19
+ * POR QUE NO SE ARREGLA EN EL SITIO QUE LO ROMPE
20
+ *
21
+ * Hay demasiados sitios que lo rompen, y todos con razon:
22
+ *
23
+ * - `InterruptWatcher.stop()` devuelve el modo crudo a como estaba. Si un
24
+ * vigilante se para TARDE —despues de que el prompt ya se haya dibujado—
25
+ * restaura un estado que ya no toca y se lo quita al prompt.
26
+ * - `readline.createInterface(...).close()` hace por dentro `pause()` y
27
+ * `setRawMode(false)`. Lo usan el dialogo de "¿que prefieres que haga?" y la
28
+ * pregunta del detector de bucles.
29
+ * - Cualquier salida del bucle que se escape sin parar lo suyo.
30
+ *
31
+ * Ya hubo dos intentos de arreglarlo apretando tuercas una por una, y el fallo
32
+ * volvio las dos veces, porque basta que UNA de las veinte salidas se escape.
33
+ * Asi que el prompt deja de confiar: mientras esta abierto, comprueba cuatro
34
+ * veces por segundo que el teclado sigue siendo suyo y lo recupera si no.
35
+ *
36
+ * Cuesta cuatro comparaciones cada 250 ms. El fallo costaba cerrar el terminal.
37
+ */
38
+ /**
39
+ * Devuelve el teclado al prompt si alguien se lo ha quitado.
40
+ *
41
+ * Devuelve la lista de lo que ha tenido que reparar. Vacia es lo normal: si
42
+ * alguna vez devuelve algo, hay un fallo de verdad en quien solto el teclado, y
43
+ * por eso `CHOCOLATITO_TECLADO=debug` lo saca por pantalla en vez de callarselo.
44
+ */
45
+ export function repararTeclado(entrada) {
46
+ // Sin terminal delante (una tuberia, CI, `chocolatito "tarea"`) no hay modo
47
+ // crudo que defender y forzarlo seria un error.
48
+ if (!entrada || !entrada.isTTY || typeof entrada.setRawMode !== "function")
49
+ return [];
50
+ const reparado = [];
51
+ if (entrada.isRaw !== true) {
52
+ entrada.setRawMode(true);
53
+ reparado.push("modo crudo");
54
+ }
55
+ // `pause()` corta el flujo sin avisar a nadie: el oyente de `keypress` sigue
56
+ // apuntado y no recibe ni una tecla. Es lo que deja detras un readline al
57
+ // cerrarse.
58
+ if (typeof entrada.isPaused === "function" && entrada.isPaused() && typeof entrada.resume === "function") {
59
+ entrada.resume();
60
+ reparado.push("entrada en pausa");
61
+ }
62
+ return reparado;
63
+ }
64
+ /** Cada cuanto se comprueba. Cuatro veces por segundo se nota como instantaneo. */
65
+ export const MS_ENTRE_COMPROBACIONES = 250;
@@ -0,0 +1,17 @@
1
+ import { AvisoDeBucle } from "../agent/loopDetector.js";
2
+ export interface RespuestaDeBucle {
3
+ /** "seguir" deja al agente repetir; "parar" corta esa llamada y le obliga a cambiar. */
4
+ accion: "seguir" | "parar";
5
+ indicacion?: string;
6
+ }
7
+ /**
8
+ * Que hacer cuando el agente lleva tres vueltas haciendo exactamente lo mismo.
9
+ *
10
+ * Sin terminal —CI, `chocolatito "tarea"`, un cron— se PARA. Es lo contrario de
11
+ * lo que hace el dialogo de permisos, que ante la duda deniega para no tocar
12
+ * nada, y el motivo es que aqui el riesgo es el opuesto: no hay nada que
13
+ * proteger del agente, hay una cuota que proteger de un bucle que nadie esta
14
+ * mirando. Parar no mata el turno: le devuelve al modelo un mensaje explicando
15
+ * el atasco, y el turno sigue.
16
+ */
17
+ export declare function preguntarPorBucle(aviso: AvisoDeBucle): Promise<RespuestaDeBucle>;
@@ -0,0 +1,59 @@
1
+ import readline from "node:readline";
2
+ import process from "node:process";
3
+ import chalk from "chalk";
4
+ /**
5
+ * Que hacer cuando el agente lleva tres vueltas haciendo exactamente lo mismo.
6
+ *
7
+ * Sin terminal —CI, `chocolatito "tarea"`, un cron— se PARA. Es lo contrario de
8
+ * lo que hace el dialogo de permisos, que ante la duda deniega para no tocar
9
+ * nada, y el motivo es que aqui el riesgo es el opuesto: no hay nada que
10
+ * proteger del agente, hay una cuota que proteger de un bucle que nadie esta
11
+ * mirando. Parar no mata el turno: le devuelve al modelo un mensaje explicando
12
+ * el atasco, y el turno sigue.
13
+ */
14
+ export async function preguntarPorBucle(aviso) {
15
+ const cabecera = chalk.yellow(`\n↻ Parece un bucle: `) +
16
+ chalk.white(`"${aviso.herramienta}"`) +
17
+ chalk.yellow(` lleva ${aviso.repeticiones} llamadas identicas con el mismo resultado.`);
18
+ if (!process.stdin.isTTY) {
19
+ console.log(cabecera + chalk.gray("\n Sin terminal interactiva: se corta la repeticion para no gastar cuota.\n"));
20
+ return { accion: "parar" };
21
+ }
22
+ console.log(cabecera);
23
+ console.log(chalk.gray(" ¿Le dejo seguir intentandolo, o le digo que cambie de enfoque?\n"));
24
+ return new Promise((resolve) => {
25
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
26
+ // Como en el dialogo de permisos: 'close' llega tambien tras una respuesta
27
+ // normal, y sin guarda el mensaje de corte pisaria lo que el usuario acaba
28
+ // de escribir. Manda quien llegue primero.
29
+ let asentada = false;
30
+ const asentar = (r) => {
31
+ if (asentada)
32
+ return;
33
+ asentada = true;
34
+ resolve(r);
35
+ };
36
+ // Ctrl+C, Ctrl+D o stdin cerrandose significan lo mismo: no hay respuesta.
37
+ // Aqui eso es "para", que es la opcion que no cuesta dinero.
38
+ rl.on("close", () => {
39
+ // readline duerme su entrada al cerrarse (`close()` llama a `pause()`).
40
+ // Aqui estamos a mitad de turno: si se queda dormida, el vigilante que
41
+ // sigue no recibe ni una tecla y el Esc deja de cancelar.
42
+ process.stdin.resume();
43
+ if (asentada)
44
+ return;
45
+ process.stdout.write("\n");
46
+ asentar({ accion: "parar" });
47
+ });
48
+ rl.question(chalk.hex("#A855F7")(" [enter] que cambie de enfoque · [s] dejale seguir · o escribe una indicacion ❯ "), (respuesta) => {
49
+ const texto = respuesta.trim();
50
+ if (texto.toLowerCase() === "s" || texto.toLowerCase() === "si") {
51
+ asentar({ accion: "seguir" });
52
+ }
53
+ else {
54
+ asentar({ accion: "parar", indicacion: texto || undefined });
55
+ }
56
+ rl.close();
57
+ });
58
+ });
59
+ }
@@ -2,6 +2,7 @@ import readline from "node:readline";
2
2
  import process from "node:process";
3
3
  import chalk from "chalk";
4
4
  import { cycleMode, describeMode, getMode, modeAnnouncement } from "./modes.js";
5
+ import { isOutsideProject, isSvgSource } from "../config/permissions.js";
5
6
  /**
6
7
  * Aprobacion automatica: se activa con --yes / -y o con la variable de entorno
7
8
  * CHOCOLATITO_AUTO_APPROVE. Pensada para modo no interactivo y CI.
@@ -77,9 +78,45 @@ export async function askToolPermission(toolName, args, cwd = process.cwd()) {
77
78
  input: process.stdin,
78
79
  output: process.stdout,
79
80
  });
81
+ // Guarda de resolucion unica: 'close' llega tambien despues de una
82
+ // respuesta normal, y sin ella el mensaje de cancelado pisaria al texto
83
+ // que el usuario acaba de escribir. Manda quien llegue primero.
84
+ let asentada = false;
85
+ const asentar = (texto) => {
86
+ if (asentada)
87
+ return;
88
+ asentada = true;
89
+ resFeedback(texto);
90
+ };
91
+ // Ctrl+C aqui dentro no llama al callback de 'line': readline cierra la
92
+ // interfaz y punto. Sin este oyente la promesa no se asentaba nunca, el
93
+ // `await` de askToolPermission no volvia y el bucle del agente se
94
+ // quedaba vivo pero inerte: ni ejecutaba, ni cancelaba, ni devolvia el
95
+ // prompt. La unica salida era un segundo Ctrl+C, que ya llegaba con el
96
+ // terminal en modo cocido y mataba la sesion entera.
97
+ //
98
+ // Vale igual para Ctrl+D y para stdin cerrandose por su cuenta: todo lo
99
+ // que cierre la interfaz sin linea significa lo mismo, que no hay
100
+ // respuesta, y eso se traduce en cancelar la accion.
101
+ rl.on("close", () => {
102
+ if (asentada)
103
+ return;
104
+ // El cursor se queda a medio renglon, pegado al "❯": se cierra la
105
+ // linea para que lo siguiente no salga encima del prompt.
106
+ process.stdout.write("\n");
107
+ asentar("Cancelado por el usuario con Ctrl+C.");
108
+ });
109
+ // Un readline, al cerrarse, hace por dentro `pause()` sobre su entrada
110
+ // ademas de apagar el modo crudo. Quien venga detras —el vigilante del
111
+ // resto del turno— da por hecho que la entrada fluye, se apunta como
112
+ // oyente y no recibe ni una tecla: el Esc deja de cancelar y lo que se
113
+ // teclee no se encola. Se despierta aqui, que es donde se durmio.
114
+ rl.on("close", () => {
115
+ process.stdin.resume();
116
+ });
80
117
  rl.question(chalk.hex("#A855F7")("\n¿Qué prefieres que haga? ❯ "), (answer) => {
118
+ asentar(answer.trim() || "El usuario canceló la acción sin dar detalles adicionales.");
81
119
  rl.close();
82
- resFeedback(answer.trim() || "El usuario canceló la acción sin dar detalles adicionales.");
83
120
  });
84
121
  });
85
122
  }
@@ -235,6 +272,10 @@ function classify(toolName, args) {
235
272
  return { title: " Controlar tu ordenador", color: "#A855F7" };
236
273
  return { title: " Permiso de ejecución", color: "#D97757" };
237
274
  }
275
+ /** Herramientas que conducen pestanas del navegador del usuario. */
276
+ function esNavegador(toolName) {
277
+ return toolName === "chrome" || toolName === "browser" || toolName === "browser_live";
278
+ }
238
279
  /** Las lineas de detalle, ya recortadas para que quepan dentro del marco. */
239
280
  function details(toolName, args, cwd, width) {
240
281
  const max = width - 8;
@@ -258,14 +299,55 @@ function details(toolName, args, cwd, width) {
258
299
  add(plain, `${chalk.green(`+${mas} líneas`)} ${chalk.red(`-${menos} líneas`)}`);
259
300
  }
260
301
  }
302
+ else if (typeof args.outputPath === "string") {
303
+ // create_image no trae ni `command` ni `path`, asi que el dialogo caia en el
304
+ // caso de abajo y pedia aprobar la palabra "create_image" a secas: ni el
305
+ // destino ni el detalle de que el generatorScript se ejecuta con python.
306
+ add(args.outputPath, chalk.bold.white(args.outputPath));
307
+ const script = typeof args.generatorScript === "string" ? args.generatorScript : "";
308
+ const lineas = script.split("\n").length;
309
+ if (isSvgSource(script)) {
310
+ add(`SVG, ${lineas} líneas`, chalk.gray(`SVG, ${lineas} líneas`));
311
+ }
312
+ else {
313
+ const plain = `ejecuta ${lineas} líneas de Python`;
314
+ add(plain, chalk.hex("#F59E0B")(plain));
315
+ }
316
+ }
317
+ else if (typeof args.sourcePath === "string") {
318
+ // move_copy_file no trae `path` ni `outputPath`, asi que caia en el caso de
319
+ // abajo y el dialogo pedia aprobar la palabra "move_copy_file" a secas. Lo
320
+ // que hay que ver aqui es justo lo contrario: que archivo se pisa.
321
+ add(args.sourcePath, chalk.bold.white(args.sourcePath));
322
+ const destino = String(args.destinationPath ?? "");
323
+ add(`-> ${destino}`, `${chalk.gray("->")} ${chalk.bold.white(destino)}`);
324
+ if (args.overwrite === true) {
325
+ const aviso = "el destino ya existe y su contenido se pierde";
326
+ add(aviso, chalk.hex("#EF4444")(aviso));
327
+ }
328
+ }
261
329
  else if (typeof args.action === "string") {
262
- const extra = args.url || args.appName || args.window || args.text || "";
330
+ // Sobre QUE actua importa tanto como la accion. Un "close_tab" a secas no
331
+ // decia que pestana moria, asi que aprobarlo era aprobar a ciegas.
332
+ const objetivo = args.url || args.appName || args.window || args.text ||
333
+ (typeof args.tabId === "number" ? `pestaña ${args.tabId}` : "") ||
334
+ (typeof args.ref === "number" ? `elemento [${args.ref}]` : "") ||
335
+ "";
336
+ const extra = objetivo || (esNavegador(toolName) ? "en la pestaña de trabajo" : "");
263
337
  const plain = extra ? `${args.action} ${String(extra)}` : String(args.action);
264
338
  add(plain, extra ? `${chalk.bold.white(args.action)} ${chalk.gray(String(extra))}` : chalk.bold.white(args.action));
265
339
  }
266
340
  else {
267
341
  add(toolName, chalk.bold.white(toolName));
268
342
  }
343
+ // Una ruta larga se lee igual venga de donde venga: "C:\Users\yo\proyecto\src"
344
+ // y "C:\Windows\System32" ocupan lo mismo en el marco. Este es justo el
345
+ // dialogo que se aprueba de un vistazo, asi que el motivo por el que aparece
346
+ // va escrito con letras.
347
+ if ([args.path, args.outputPath, args.sourcePath, args.destinationPath].some((r) => isOutsideProject(cwd, r))) {
348
+ const aviso = "fuera del proyecto";
349
+ add(aviso, chalk.hex("#EF4444")(aviso));
350
+ }
269
351
  out.push({ text: "", length: 0 });
270
352
  return out;
271
353
  }
@@ -4,4 +4,39 @@ export interface SlashCommandItem {
4
4
  args?: string;
5
5
  }
6
6
  export declare const SLASH_COMMANDS: SlashCommandItem[];
7
+ export interface DropdownItem {
8
+ type: "command" | "file";
9
+ label: string;
10
+ secondary?: string;
11
+ insertText: string;
12
+ }
13
+ export interface PromptFrameOptions {
14
+ cols: number;
15
+ /** Alto de la ventana; sirve para no dibujar un marco que no quepa. */
16
+ rows?: number;
17
+ promptSymbol?: string;
18
+ input?: string;
19
+ cursorIndex?: number;
20
+ items?: DropdownItem[];
21
+ selectedIndex?: number;
22
+ }
23
+ export interface PromptFrame {
24
+ /** Lineas LOGICAS. Cualquiera puede ocupar mas de una fila fisica. */
25
+ lines: string[];
26
+ /** Filas de pantalla que ocupara el marco entero con este ancho. */
27
+ totalRows: number;
28
+ /** Fila fisica del cursor, con 0 en la primera fila del marco. */
29
+ cursorRow: number;
30
+ cursorCol: number;
31
+ /** Primer elemento del desplegable que se muestra (ventana deslizante). */
32
+ dropdownOffset: number;
33
+ }
34
+ /**
35
+ * Arma el marco del prompt y dice exactamente donde queda el cursor.
36
+ *
37
+ * Va aparte de render() por dos razones: se puede probar sin terminal, y la
38
+ * aritmetica de filas se hace UNA vez sobre las mismas lineas que se pintan, en
39
+ * lugar de estimarse por otro lado y descuadrarse.
40
+ */
41
+ export declare function buildPromptFrame(opts: PromptFrameOptions): PromptFrame;
7
42
  export declare function askInteractivePrompt(promptSymbol?: string, cwd?: string): Promise<string>;