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.
- package/README.md +89 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +536 -78
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +222 -34
- package/dist/mcp/client.d.ts +22 -0
- package/dist/mcp/client.js +93 -11
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +202 -18
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +9 -3
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +57 -4
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.js +18 -1
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +56 -5
- package/dist/ui/keyboardGuard.d.ts +55 -0
- package/dist/ui/keyboardGuard.js +65 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +35 -0
- package/dist/ui/prompt.js +345 -125
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/extension/README.md +95 -74
- package/extension/background.js +598 -178
- package/extension/content.js +92 -9
- package/extension/manifest.json +2 -1
- package/extension/popup.js +28 -12
- package/package.json +1 -1
package/dist/ui/interrupt.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/ui/prompt.d.ts
CHANGED
|
@@ -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>;
|