chocolatito-code 1.6.6 → 1.6.7
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/dist/index.js +65 -33
- package/dist/ui/ink/App.d.ts +0 -23
- package/dist/ui/ink/App.js +86 -57
- package/dist/ui/ink/Prompt.d.ts +16 -6
- package/dist/ui/ink/Prompt.js +37 -62
- package/dist/ui/ink/montarApp.d.ts +34 -0
- package/dist/ui/ink/montarApp.js +93 -1
- package/dist/ui/ink/prestamo.d.ts +63 -0
- package/dist/ui/ink/prestamo.js +92 -0
- package/dist/ui/ink/teclado.d.ts +48 -0
- package/dist/ui/ink/teclado.js +96 -0
- package/dist/ui/ink/transcripcion.d.ts +114 -0
- package/dist/ui/ink/transcripcion.js +180 -0
- package/dist/ui/interrupt.js +4 -1
- package/dist/ui/loopPrompt.d.ts +6 -7
- package/dist/ui/loopPrompt.js +14 -1
- package/dist/ui/marco.d.ts +2 -0
- package/dist/ui/marco.js +18 -1
- package/dist/ui/pantalla.d.ts +45 -17
- package/dist/ui/pantalla.js +150 -92
- package/dist/ui/permissionPrompt.d.ts +10 -17
- package/dist/ui/permissionPrompt.js +14 -1
- package/dist/ui/pieFijo.js +26 -4
- package/dist/ui/selector.d.ts +6 -4
- package/dist/ui/selector.js +14 -1
- package/extension/manifest.json +60 -60
- package/package.json +1 -1
- package/dist/ui/historial.d.ts +0 -49
- package/dist/ui/historial.js +0 -92
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ import { codigoDeSalida, EXIT_OK, EXIT_FALLO, EXIT_USO, EXIT_LICENCIA } from "./
|
|
|
15
15
|
import { showBanner, animateStartupBanner, showHelp, showEffortMenu } from "./ui/banner.js";
|
|
16
16
|
import { fijarMotor } from "./ui/motorActual.js";
|
|
17
17
|
import { elegirDeLista } from "./ui/selector.js";
|
|
18
|
-
import { entrarEnPantallaFija, pantallaFijaPedida } from "./ui/pantalla.js";
|
|
19
|
-
import { montarArbol } from "./ui/ink/montarApp.js";
|
|
18
|
+
import { entrarEnPantallaFija, pantallaFijaPedida, salirDePantallaFija } from "./ui/pantalla.js";
|
|
19
|
+
import { montarArbol, desmontarArbol } from "./ui/ink/montarApp.js";
|
|
20
20
|
import { reafirmarModoCrudo } from "./ui/keyboardGuard.js";
|
|
21
21
|
import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
|
|
22
22
|
import { changeDir } from "./tools/changeDir.js";
|
|
@@ -89,6 +89,7 @@ function handleFlags(argv) {
|
|
|
89
89
|
"CONFIGURACION",
|
|
90
90
|
" API key: variable CHOCOLATITO_API_KEY, un .env del proyecto o ~/.chocolatitorc.",
|
|
91
91
|
" Reglas del proyecto: CHOCOLATITO.md. Permisos y hooks: .chocolatito/settings.json.",
|
|
92
|
+
" Interfaz clasica (sin la caja pegada abajo): variable CHOCOLATITO_PANTALLA=normal.",
|
|
92
93
|
"",
|
|
93
94
|
"CODIGOS DE SALIDA (modo orden unica)",
|
|
94
95
|
" 0 La tarea se completo.",
|
|
@@ -136,9 +137,29 @@ function instalarRedDeSeguridad() {
|
|
|
136
137
|
});
|
|
137
138
|
// Una excepcion no capturada si es terminal, pero al menos se sale con la
|
|
138
139
|
// terminal usable y con un mensaje legible en vez de una traza cruda.
|
|
140
|
+
//
|
|
141
|
+
// NO SE USA `console.log` AQUI, Y ES A PROPOSITO
|
|
142
|
+
//
|
|
143
|
+
// Con el arbol montado la consola esta interceptada: un `console.log` va a la
|
|
144
|
+
// transcripcion, que la pinta el arbol... en el proximo dibujado, que ya no
|
|
145
|
+
// llega porque la linea siguiente cierra el proceso. El mensaje se escribe en
|
|
146
|
+
// un sitio que nadie va a leer nunca.
|
|
147
|
+
//
|
|
148
|
+
// Es exactamente el fallo que impidio diagnosticar la pantalla en negro dos
|
|
149
|
+
// veces: el programa se cerraba "sin decir nada" y si lo decia, pero a una
|
|
150
|
+
// pantalla que se iba con el. Aqui se suelta el arbol primero -que devuelve la
|
|
151
|
+
// consola de verdad- y se escribe directo a stderr, que no lo intercepta nadie.
|
|
139
152
|
process.on("uncaughtException", (err) => {
|
|
140
153
|
restaurarTerminal();
|
|
141
|
-
|
|
154
|
+
try {
|
|
155
|
+
desmontarArbol();
|
|
156
|
+
}
|
|
157
|
+
catch { }
|
|
158
|
+
try {
|
|
159
|
+
salirDePantallaFija();
|
|
160
|
+
}
|
|
161
|
+
catch { }
|
|
162
|
+
process.stderr.write(chalk.red(`\n✖ Error inesperado: ${err?.message || String(err)}\n`) +
|
|
142
163
|
chalk.gray(`${(err?.stack || "").split("\n").slice(1, 4).join("\n")}\n`));
|
|
143
164
|
process.exit(EXIT_FALLO);
|
|
144
165
|
});
|
|
@@ -190,8 +211,8 @@ async function main() {
|
|
|
190
211
|
// errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
|
|
191
212
|
const nodeViejo = comprobarNode();
|
|
192
213
|
if (nodeViejo) {
|
|
193
|
-
console.error(chalk.red(`
|
|
194
|
-
${nodeViejo}
|
|
214
|
+
console.error(chalk.red(`
|
|
215
|
+
${nodeViejo}
|
|
195
216
|
`));
|
|
196
217
|
process.exit(EXIT_USO);
|
|
197
218
|
}
|
|
@@ -237,16 +258,6 @@ ${nodeViejo}
|
|
|
237
258
|
const hookManager = new HookManager(context.cwd);
|
|
238
259
|
const sessionManager = new SessionManager();
|
|
239
260
|
const sessionId = `session_${Date.now()}`;
|
|
240
|
-
// La barra de abajo solo puede quedarse clavada si el programa toma la
|
|
241
|
-
// pantalla entera. Se pide con CHOCOLATITO_PANTALLA=fija y se paga con el
|
|
242
|
-
// historial del terminal: ver ui/pantalla.ts.
|
|
243
|
-
if (pantallaFijaPedida()) {
|
|
244
|
-
entrarEnPantallaFija();
|
|
245
|
-
// Entrar y empezar a dibujar son la misma decision: entrar sin montar el
|
|
246
|
-
// arbol deja una consola en blanco de la que solo se sale cerrando la
|
|
247
|
-
// ventana.
|
|
248
|
-
montarArbol();
|
|
249
|
-
}
|
|
250
261
|
let currentModel = DEFAULT_ESPECTRO;
|
|
251
262
|
// La barra de estado lo lee de aqui. Fijarlo al arrancar y no solo al
|
|
252
263
|
// cambiarlo: si no, hasta el primer /effort la barra no dice nada.
|
|
@@ -350,6 +361,27 @@ ${nodeViejo}
|
|
|
350
361
|
chalk.gray(" shift+tab cambia el modo de permisos · puedes escribir mientras trabaja y se encola\n\n"));
|
|
351
362
|
if (getMode() !== "default")
|
|
352
363
|
console.log(` ${modeAnnouncement()}\n`);
|
|
364
|
+
// EL ARBOL SE MONTA AQUI Y NO ANTES
|
|
365
|
+
//
|
|
366
|
+
// Va de serie. CHOCOLATITO_PANTALLA=normal devuelve la clasica. Ver ui/pantalla.ts.
|
|
367
|
+
//
|
|
368
|
+
// Estaba montado mucho mas arriba, antes del zorro. Y el zorro se dibuja con
|
|
369
|
+
// animacion: mueve el cursor y repinta. Dos cosas moviendo el cursor a la vez
|
|
370
|
+
// descuadran la cuenta de filas de Ink desde el primer fotograma, y lo que se
|
|
371
|
+
// ve es "sale una barra y lo demas negro".
|
|
372
|
+
//
|
|
373
|
+
// Aqui no queda nada por animar: el zorro ya salio, y lo que venga sale por el
|
|
374
|
+
// arbol o por el parche de console de Ink.
|
|
375
|
+
//
|
|
376
|
+
// Y el arbol solo se monta si se entro de VERDAD: sin terminal -salida a un
|
|
377
|
+
// archivo, un script, CI- no se entra, e Ink sin terminal escribe su marco
|
|
378
|
+
// como texto suelto. La salida del programa saldria con la caja de escribir
|
|
379
|
+
// metida dentro.
|
|
380
|
+
if (pantallaFijaPedida() && entrarEnPantallaFija()) {
|
|
381
|
+
// Entrar y empezar a dibujar son la misma decision: desviar el dibujo sin
|
|
382
|
+
// que haya nadie pintandolo deja la consola muda.
|
|
383
|
+
montarArbol();
|
|
384
|
+
}
|
|
353
385
|
// Lo que el usuario escribio mientras el agente trabajaba entra como siguiente
|
|
354
386
|
// orden, sin tener que volver a teclearlo.
|
|
355
387
|
let pendiente = null;
|
|
@@ -421,15 +453,15 @@ ${nodeViejo}
|
|
|
421
453
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
422
454
|
.map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
|
|
423
455
|
.join("\n---\n\n");
|
|
424
|
-
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
425
|
-
**Fecha:** ${new Date().toLocaleString()}
|
|
426
|
-
**Directorio:** \`${agent.currentCwd}\`
|
|
427
|
-
**Motor:** ${currentModel.name}
|
|
428
|
-
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
${sessionData}
|
|
456
|
+
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
457
|
+
**Fecha:** ${new Date().toLocaleString()}
|
|
458
|
+
**Directorio:** \`${agent.currentCwd}\`
|
|
459
|
+
**Motor:** ${currentModel.name}
|
|
460
|
+
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
${sessionData}
|
|
433
465
|
`;
|
|
434
466
|
fs.writeFileSync(exportPath, mdContent, "utf-8");
|
|
435
467
|
console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
|
|
@@ -523,12 +555,12 @@ ${sessionData}
|
|
|
523
555
|
}
|
|
524
556
|
console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
|
|
525
557
|
console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
|
|
526
|
-
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
527
|
-
Tu objetivo principal es: "${goalText}"
|
|
528
|
-
Directivas de ejecución:
|
|
529
|
-
1. Formula un plan de ejecución mental paso a paso.
|
|
530
|
-
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
531
|
-
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
558
|
+
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
559
|
+
Tu objetivo principal es: "${goalText}"
|
|
560
|
+
Directivas de ejecución:
|
|
561
|
+
1. Formula un plan de ejecución mental paso a paso.
|
|
562
|
+
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
563
|
+
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
532
564
|
4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
|
|
533
565
|
renderUserPrompt(`/goal ${goalText}`);
|
|
534
566
|
await agent.runTask(autonomousPrompt);
|
|
@@ -632,12 +664,12 @@ Directivas de ejecución:
|
|
|
632
664
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
633
665
|
const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
634
666
|
agent.setEspectroModel(currentModel, updatedPrompt);
|
|
635
|
-
console.log(chalk.green(`
|
|
667
|
+
console.log(chalk.green(`
|
|
636
668
|
✔ Nivel actualizado a `) +
|
|
637
669
|
chalk.bold.hex("#A855F7")(currentModel.name) +
|
|
638
670
|
chalk.gray(` (${currentModel.effortLevel})`) +
|
|
639
|
-
`
|
|
640
|
-
${chalk.italic.gray(currentModel.description)}
|
|
671
|
+
`
|
|
672
|
+
${chalk.italic.gray(currentModel.description)}
|
|
641
673
|
`);
|
|
642
674
|
// EL TECLADO SE QUEDABA TRABADO AL CAMBIAR DE MOTOR
|
|
643
675
|
//
|
package/dist/ui/ink/App.d.ts
CHANGED
|
@@ -1,25 +1,2 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
/**
|
|
3
|
-
* EL ÁRBOL ÚNICO
|
|
4
|
-
*
|
|
5
|
-
* POR QUE EXISTE
|
|
6
|
-
*
|
|
7
|
-
* Antes Ink solo era dueño del prompt: se montaba para pedir una orden y se
|
|
8
|
-
* desmontaba al enviarla. Mientras el agente trabajaba, el terminal volvía a ser
|
|
9
|
-
* de siempre y la barra de abajo la pintaba `pieFijo` borrando y repintando a
|
|
10
|
-
* mano.
|
|
11
|
-
*
|
|
12
|
-
* Eso son DOS dueños del mismo trozo de pantalla turnándose, y de ahí salían
|
|
13
|
-
* casi todas las quejas: la barra que sube con el scroll, el hueco que aparece,
|
|
14
|
-
* el marco que se descuadra. Cada arreglo era coordinar mejor a los dos dueños.
|
|
15
|
-
*
|
|
16
|
-
* Aquí hay uno solo, y vive toda la sesión. Lo que sale del agente entra en el
|
|
17
|
-
* historial (historial.ts) y se pinta arriba; abajo va SIEMPRE la barra, sea la
|
|
18
|
-
* de escribir o la de trabajar. Nadie tiene que ponerse de acuerdo con nadie.
|
|
19
|
-
*
|
|
20
|
-
* Y POR ESO LA BARRA SE QUEDA QUIETA
|
|
21
|
-
*
|
|
22
|
-
* No porque se repinte rápido, sino porque es la última fila de un árbol que
|
|
23
|
-
* ocupa la ventana entera. No hay nada que pueda moverla, ni la rueda del ratón.
|
|
24
|
-
*/
|
|
25
2
|
export declare function App(): React.ReactElement;
|
package/dist/ui/ink/App.js
CHANGED
|
@@ -1,79 +1,108 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { Box, Text, useStdout } from "ink";
|
|
3
|
+
import { Box, Static, Text, useApp, useStdout } from "ink";
|
|
4
4
|
import { Prompt } from "./Prompt.js";
|
|
5
|
-
import {
|
|
5
|
+
import { colgando, pendientes, recortarCola } from "./transcripcion.js";
|
|
6
|
+
import { alCambiar } from "./transcripcion.js";
|
|
6
7
|
import { cuandoCambieElPie, lineasDelPie } from "../pieFijo.js";
|
|
7
8
|
import { alCambiarElTurno, estadoDelTurno } from "./control.js";
|
|
9
|
+
import { registrarPrestamo } from "./prestamo.js";
|
|
10
|
+
import { useTecladoSiempreNuestro } from "./teclado.js";
|
|
8
11
|
/**
|
|
9
|
-
* EL ÁRBOL ÚNICO
|
|
12
|
+
* EL ÁRBOL ÚNICO, EN LA PANTALLA DE SIEMPRE
|
|
10
13
|
*
|
|
11
|
-
*
|
|
14
|
+
* DE DÓNDE VIENE ESTE ARCHIVO
|
|
12
15
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* mano.
|
|
16
|
+
* La versión anterior tomaba la pantalla alternativa —la de vim o htop— para
|
|
17
|
+
* clavar la barra de abajo. Funcionaba en el emulador y se publicó rota dos
|
|
18
|
+
* veces. Y sobre todo: no era lo que hace Claude Code, que era lo que se pedía.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* el
|
|
20
|
+
* Claude Code renderiza en el buffer NORMAL. La conversación se emite una sola
|
|
21
|
+
* vez con `<Static>` y pasa al historial del terminal; debajo queda una zona que
|
|
22
|
+
* se repinta con el spinner y la caja de escribir. Por eso allí la rueda del
|
|
23
|
+
* ratón funciona y se puede releer la sesión entera.
|
|
21
24
|
*
|
|
22
|
-
*
|
|
23
|
-
* historial (historial.ts) y se pinta arriba; abajo va SIEMPRE la barra, sea la
|
|
24
|
-
* de escribir o la de trabajar. Nadie tiene que ponerse de acuerdo con nadie.
|
|
25
|
+
* LO QUE ESO SIGNIFICA, DICHO CLARO
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
+
* La barra está siempre pegada al final de lo que ha salido, y cualquier línea
|
|
28
|
+
* nueva te devuelve ahí. Pero si subes con la rueda, la barra sube con el resto:
|
|
29
|
+
* es texto del terminal, no una capa encima. En Claude Code pasa exactamente lo
|
|
30
|
+
* mismo. Clavarla de verdad solo se consigue tomando la pantalla entera, y el
|
|
31
|
+
* precio es quedarse sin historial ni rueda del ratón.
|
|
27
32
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
33
|
+
* LAS TRES ZONAS
|
|
34
|
+
*
|
|
35
|
+
* <Static> la conversación, escrita una vez y para siempre
|
|
36
|
+
* cola el trozo de línea que el modelo está escribiendo ahora mismo
|
|
37
|
+
* barra el spinner, o la caja de escribir
|
|
38
|
+
*
|
|
39
|
+
* Las dos últimas se repintan; la primera no puede.
|
|
40
|
+
*/
|
|
41
|
+
/** Filas que se reservan para la caja cuando el prompt está puesto. */
|
|
42
|
+
const ALTO_DE_LA_CAJA = 6;
|
|
43
|
+
/**
|
|
44
|
+
* UNA LÍNEA VACÍA TIENE QUE OCUPAR SU FILA
|
|
45
|
+
*
|
|
46
|
+
* Un `<Text>` sin contenido mide cero y desaparece. Y las líneas en blanco de la
|
|
47
|
+
* respuesta no son adorno: son lo que separa un párrafo del siguiente, y lo que
|
|
48
|
+
* deja aire entre el mensaje que mandaste y lo que contesta el modelo.
|
|
49
|
+
*
|
|
50
|
+
* Sin esto, la conversación sale apelmazada: los párrafos pegados unos a otros y
|
|
51
|
+
* la lista de herramientas encima del texto sin un respiro. Se vio al mirar la
|
|
52
|
+
* pantalla emulada; ninguna prueba lo cazaba, porque todas comprobaban qué
|
|
53
|
+
* líneas salen y ninguna cuántas.
|
|
30
54
|
*/
|
|
55
|
+
function conCuerpo(texto) {
|
|
56
|
+
return texto.length > 0 ? texto : " ";
|
|
57
|
+
}
|
|
31
58
|
export function App() {
|
|
32
59
|
const { stdout } = useStdout();
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
// Ink escribe su arbol y detras un salto de linea. Si el arbol mide justo lo
|
|
36
|
-
// que la ventana, ese salto la desborda: el terminal desplaza una fila, y como
|
|
37
|
-
// Ink repinta subiendo el numero de filas que escribio, a partir de ahi borra
|
|
38
|
-
// una fila que ya no es la suya. En la pantalla alternativa, que no tiene
|
|
39
|
-
// historial al que recurrir, eso degenera en pantalla en negro.
|
|
40
|
-
//
|
|
41
|
-
// Es lo que se publico roto en la 1.6.0: "esta fallando, no sale nada". Las
|
|
42
|
-
// pruebas no lo cazan porque el terminal de mentira de la libreria no desplaza.
|
|
43
|
-
const filas = Math.max(6, (stdout?.rows || 24) - 1);
|
|
60
|
+
const { suspendTerminal } = useApp();
|
|
44
61
|
const [, setRedibujar] = useState(0);
|
|
45
|
-
const [subido, setSubido] = useState(0);
|
|
46
62
|
const [turno, setTurno] = useState(estadoDelTurno());
|
|
47
63
|
const repintar = () => setRedibujar((n) => n + 1);
|
|
48
|
-
// Tres fuentes de cambio,
|
|
49
|
-
// que publica el spinner, y el paso de "esperando orden" a "trabajando".
|
|
50
|
-
useEffect(() =>
|
|
64
|
+
// Tres fuentes de cambio, las tres fuera de React: lo que escribe el agente,
|
|
65
|
+
// lo que publica el spinner, y el paso de "esperando orden" a "trabajando".
|
|
66
|
+
useEffect(() => alCambiar(repintar), []);
|
|
51
67
|
useEffect(() => cuandoCambieElPie(repintar), []);
|
|
52
68
|
useEffect(() => alCambiarElTurno((nuevo) => setTurno(nuevo)), []);
|
|
53
|
-
//
|
|
54
|
-
//
|
|
69
|
+
// El diálogo de permisos y los menús necesitan el teclado para ellos solos, y
|
|
70
|
+
// se abren con el árbol vivo. Aquí se deja apuntado el préstamo que Ink ya
|
|
71
|
+
// trae para eso; ver ink/prestamo.ts.
|
|
55
72
|
useEffect(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
registrarPrestamo(suspendTerminal);
|
|
74
|
+
return () => registrarPrestamo(null);
|
|
75
|
+
}, [suspendTerminal]);
|
|
76
|
+
// El teclado tiene que seguir siendo de la caja aunque un diálogo, un menú o
|
|
77
|
+
// un proceso hijo lo suelten por el camino. Ver ink/teclado.ts: es el fallo
|
|
78
|
+
// del "no puedo escribir hasta que pulso Enter".
|
|
79
|
+
useTecladoSiempreNuestro(turno.esperando);
|
|
80
|
+
const cols = Math.max(20, stdout?.columns || 80);
|
|
81
|
+
const filas = Math.max(6, stdout?.rows || 24);
|
|
82
|
+
const permanentes = pendientes();
|
|
83
|
+
const barra = turno.esperando ? [] : lineasDelPie();
|
|
84
|
+
// EL TECHO DE LA ZONA DE ABAJO, Y POR QUÉ SE MIDE TAN DE CERCA
|
|
85
|
+
//
|
|
86
|
+
// En Windows basta con que el bloque dinámico MIDA lo que la ventana —no que
|
|
87
|
+
// se pase, que lo iguale— para que Ink cambie de repintado por diferencias a
|
|
88
|
+
// `clearTerminal` + reimprimir toda la conversación acumulada, en cada
|
|
89
|
+
// fotograma (ink.js:100-102). Con el spinner a 30 por segundo eso es la
|
|
90
|
+
// pantalla entera parpadeando y el coste creciendo con la sesión.
|
|
91
|
+
//
|
|
92
|
+
// Así que la zona de abajo se queda SIEMPRE por debajo de las filas de la
|
|
93
|
+
// ventana. Una fila de margen, y quien se pasa es la cola, que se recorta.
|
|
94
|
+
const techo = Math.max(1, filas - 1);
|
|
95
|
+
const sitioParaLaCola = Math.max(0, techo - (turno.esperando ? ALTO_DE_LA_CAJA : barra.length));
|
|
96
|
+
const cola = recortarCola(colgando(), cols, sitioParaLaCola);
|
|
97
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: permanentes, children: (linea) => _jsx(Text, { children: conCuerpo(linea.texto) }, linea.id) }), _jsxs(Box, { flexDirection: "column", children: [cola.map((linea, i) => (_jsx(Text, { children: conCuerpo(linea) }, `cola-${i}`))), turno.esperando ? (_jsx(Prompt, { alEnviar: turno.responder, alCancelar: () => turno.responder("/exit"), cwd: turno.cwd, promptSymbol: turno.promptSymbol, avisos: [],
|
|
98
|
+
// NO SE DESMONTA EL ÁRBOL AL ENVIAR
|
|
99
|
+
//
|
|
100
|
+
// El prompt nació montándose y desmontándose en cada vuelta, y para
|
|
101
|
+
// eso llamaba a `exit()` de Ink al enviar. Dentro del árbol único ese
|
|
102
|
+
// `exit()` no cierra un prompt: cierra la aplicación ENTERA.
|
|
103
|
+
//
|
|
104
|
+
// Es el fallo que se publicó como "escribo y se cierra": la primera
|
|
105
|
+
// vez que pulsabas Enter el árbol moría, ya no quedaba nadie
|
|
106
|
+
// pintando, y la promesa que esperaba la orden no se resolvía jamás.
|
|
107
|
+
cerrarAlEnviar: false, alto: Math.max(3, techo - cola.length) })) : (barra.map((linea, i) => _jsx(Text, { children: conCuerpo(linea) }, `t-${i}`)))] })] }));
|
|
79
108
|
}
|
package/dist/ui/ink/Prompt.d.ts
CHANGED
|
@@ -39,12 +39,22 @@ export interface PromptProps {
|
|
|
39
39
|
/** Mensajes que llegaron tarde y hay que dejar por encima del marco. */
|
|
40
40
|
avisos: string[];
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* ¿Al enviar hay que cerrar el árbol de Ink?
|
|
43
43
|
*
|
|
44
|
-
*
|
|
44
|
+
* Sí cuando el prompt ES el árbol —se monta para pedir una orden y se
|
|
45
|
+
* desmonta al recibirla— y NO cuando vive dentro del árbol único de App: allí
|
|
46
|
+
* `exit()` no cierra el prompt, cierra la aplicación entera y deja la promesa
|
|
47
|
+
* de la orden colgada para siempre. Ese fue el "escribo y se cierra".
|
|
45
48
|
*/
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
+
cerrarAlEnviar?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Filas de las que puede disponer el marco.
|
|
52
|
+
*
|
|
53
|
+
* App se lo dice porque ahí arriba hay más cosas —el trozo de línea que el
|
|
54
|
+
* modelo está escribiendo— y el bloque de abajo entero tiene que quedarse por
|
|
55
|
+
* debajo del alto de la ventana. Sin esto, el marco se mide contra la ventana
|
|
56
|
+
* como si estuviera solo.
|
|
57
|
+
*/
|
|
58
|
+
alto?: number;
|
|
49
59
|
}
|
|
50
|
-
export declare function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos,
|
|
60
|
+
export declare function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, cerrarAlEnviar, alto, }: PromptProps): React.ReactElement;
|
package/dist/ui/ink/Prompt.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
4
4
|
import { buildPromptFrame } from "../marco.js";
|
|
5
5
|
import { cycleMode } from "../modes.js";
|
|
6
6
|
import { aplicarOpcion, archivosDelProyecto, leerHistorial, mencionEnCurso, opcionesDelDesplegable, } from "../entrada.js";
|
|
7
7
|
import { esGrande, guardarPegado, marcaQueTerminaEn } from "../pegados.js";
|
|
8
|
-
import { alEscribir, tope, ventana } from "../historial.js";
|
|
9
|
-
import { enPantallaFija } from "../pantalla.js";
|
|
10
8
|
/** Marca del cursor. Ink esconde el de verdad, así que este se dibuja. */
|
|
11
9
|
const CARET = "█";
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* UNA LÍNEA VACÍA TIENE QUE OCUPAR SU FILA
|
|
12
|
+
*
|
|
13
|
+
* Un `<Text>` sin contenido mide cero y desaparece. Y el marco empieza con dos
|
|
14
|
+
* líneas en blanco a propósito: es el aire entre la conversación y la caja, que
|
|
15
|
+
* se pidió expresamente ("pon más espacio ahí para que se vea exacto y chévere",
|
|
16
|
+
* y después "que sea bien pues").
|
|
17
|
+
*
|
|
18
|
+
* Sin esto el aire se lo come Ink y la caja queda pegada a la última frase. No
|
|
19
|
+
* lo cazaba ninguna prueba: todas miraban QUÉ líneas salen, ninguna CUÁNTAS.
|
|
20
|
+
*/
|
|
21
|
+
function conCuerpo(texto) {
|
|
22
|
+
return texto.length > 0 ? texto : " ";
|
|
23
|
+
}
|
|
24
|
+
export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, cerrarAlEnviar = true, alto, }) {
|
|
13
25
|
const { stdout } = useStdout();
|
|
14
26
|
const { exit } = useApp();
|
|
15
27
|
const [texto, setTexto] = useState("");
|
|
@@ -19,18 +31,6 @@ export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, sinHis
|
|
|
19
31
|
const [, setRedibujar] = useState(0);
|
|
20
32
|
const historial = useRef(leerHistorial());
|
|
21
33
|
const [posHistorial, setPosHistorial] = useState(historial.current.length);
|
|
22
|
-
// Cuanto se ha subido con Re Pag. Cero es el fondo, que es donde se esta
|
|
23
|
-
// mientras se trabaja. Solo se usa en pantalla fija.
|
|
24
|
-
const [subido, setSubido] = useState(0);
|
|
25
|
-
const fija = enPantallaFija();
|
|
26
|
-
// En pantalla fija el terminal ya no guarda nada: lo que sale lo pintamos
|
|
27
|
-
// nosotros, asi que hay que repintar en cada escritura del agente. El
|
|
28
|
-
// historial vive fuera de React y React no puede adivinar que ha crecido.
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!fija)
|
|
31
|
-
return;
|
|
32
|
-
return alEscribir(() => setRedibujar((n) => n + 1));
|
|
33
|
-
}, [fija]);
|
|
34
34
|
// Se escanea UNA vez por prompt, no en cada tecla: recorrer el proyecto entero
|
|
35
35
|
// por cada letra se nota en cuanto el proyecto pasa de mediano.
|
|
36
36
|
const archivos = useMemo(() => {
|
|
@@ -44,7 +44,9 @@ export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, sinHis
|
|
|
44
44
|
const opciones = opcionesDelDesplegable(texto, cursor, archivos);
|
|
45
45
|
const indice = opciones.length > 0 ? ((elegido % opciones.length) + opciones.length) % opciones.length : 0;
|
|
46
46
|
const cols = Math.max(20, stdout?.columns || 80);
|
|
47
|
-
|
|
47
|
+
// El alto que da App ya viene descontado de lo que hay por encima; sin él, la
|
|
48
|
+
// ventana entera.
|
|
49
|
+
const rows = Math.max(6, alto ?? stdout?.rows ?? 24);
|
|
48
50
|
// El cursor se dibuja: Ink esconde el del terminal.
|
|
49
51
|
const conCaret = texto.slice(0, cursor) + CARET + texto.slice(cursor);
|
|
50
52
|
const marco = buildPromptFrame({
|
|
@@ -56,51 +58,33 @@ export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, sinHis
|
|
|
56
58
|
items: opciones,
|
|
57
59
|
selectedIndex: indice,
|
|
58
60
|
});
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// esto y no sobre el alto real: el aviso solo existe cuando ya has subido, asi
|
|
68
|
-
// que contarlo hacia que bajar no deshiciera lo que acababas de subir y el
|
|
69
|
-
// scroll derivara solo.
|
|
70
|
-
const baseDelSalto = Math.max(1, rows - marco.lines.length);
|
|
71
|
-
const altoDelTexto = Math.max(1, baseDelSalto - (subido > 0 ? 1 : 0));
|
|
61
|
+
// CERRAR EL ARBOL O NO
|
|
62
|
+
//
|
|
63
|
+
// Dentro del arbol unico, `exit()` de Ink desmonta la aplicacion entera. Ver
|
|
64
|
+
// la nota larga en la prop `cerrarAlEnviar`.
|
|
65
|
+
function cerrar() {
|
|
66
|
+
if (cerrarAlEnviar)
|
|
67
|
+
exit();
|
|
68
|
+
}
|
|
72
69
|
function enviar() {
|
|
73
70
|
const orden = texto.trim();
|
|
74
71
|
if (!orden)
|
|
75
72
|
return;
|
|
76
73
|
alEnviar(orden);
|
|
77
|
-
|
|
74
|
+
cerrar();
|
|
78
75
|
}
|
|
79
76
|
useInput((entrada, tecla) => {
|
|
80
|
-
//
|
|
77
|
+
// RE PAG Y AV PAG NO SE TOCAN, Y ESO ES LO CORRECTO
|
|
81
78
|
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// aqui no se sabe cuanto sitio hay arriba, y calcular el salto en los dos
|
|
88
|
-
// sitios con alturas distintas hacia que bajar no deshiciera lo que
|
|
89
|
-
// acababa de subir. Con la direccion, el salto sale de un solo sitio y las
|
|
90
|
-
// dos teclas son simetricas.
|
|
91
|
-
if (alDesplazar) {
|
|
92
|
-
alDesplazar(tecla.pageUp ? 1 : -1);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (fija) {
|
|
96
|
-
setSubido((n) => tecla.pageUp ? Math.min(tope(baseDelSalto), n + salto) : Math.max(0, n - salto));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
79
|
+
// Las llevaba este componente cuando la conversacion vivia en memoria y la
|
|
80
|
+
// pintabamos nosotros. Ahora la conversacion es del terminal: la rueda del
|
|
81
|
+
// raton, Re Pag y la barra de scroll de la ventana funcionan solas y mejor
|
|
82
|
+
// que cualquier cosa que hicieramos aqui. Interceptarlas seria quitarle al
|
|
83
|
+
// usuario un scroll que ya tiene.
|
|
100
84
|
// ------------------------------------------------------------- salir
|
|
101
85
|
if (tecla.ctrl && entrada === "c") {
|
|
102
86
|
alCancelar();
|
|
103
|
-
|
|
87
|
+
cerrar();
|
|
104
88
|
return;
|
|
105
89
|
}
|
|
106
90
|
// ------------------------------------------- cambiar el modo de permisos
|
|
@@ -141,7 +125,7 @@ export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, sinHis
|
|
|
141
125
|
if (arroba || soloLaBarra)
|
|
142
126
|
return;
|
|
143
127
|
alEnviar(aplicado.texto.trim());
|
|
144
|
-
|
|
128
|
+
cerrar();
|
|
145
129
|
return;
|
|
146
130
|
}
|
|
147
131
|
}
|
|
@@ -231,14 +215,5 @@ export function Prompt({ alEnviar, alCancelar, cwd, promptSymbol, avisos, sinHis
|
|
|
231
215
|
setElegido(0);
|
|
232
216
|
}
|
|
233
217
|
});
|
|
234
|
-
|
|
235
|
-
return (_jsxs(Box, { flexDirection: "column", children: [avisos.map((a, i) => (_jsx(Text, { children: a }, `aviso-${i}`))), marco.lines.map((linea, i) => (_jsx(Text, { children: linea }, i)))] }));
|
|
236
|
-
}
|
|
237
|
-
// PANTALLA FIJA: el arbol ocupa la ventana entera y la barra va la ultima, asi
|
|
238
|
-
// que no puede moverse. Lo de arriba se rellena con huecos cuando hay poca
|
|
239
|
-
// conversacion; sin eso la barra subiria a media pantalla, que es justo la
|
|
240
|
-
// queja que este modo viene a resolver.
|
|
241
|
-
const visible = ventana(altoDelTexto, subido);
|
|
242
|
-
const huecos = Math.max(0, altoDelTexto - visible.length);
|
|
243
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [Array.from({ length: huecos }, (_, i) => (_jsx(Text, { children: " " }, `hueco-${i}`))), visible.map((linea, i) => (_jsx(Text, { children: linea }, `h-${i}`))), subido > 0 && (_jsx(Text, { dimColor: true, children: ` ↑ ${subido} lineas mas arriba · Av Pag para volver al final` })), marco.lines.map((linea, i) => (_jsx(Text, { children: linea }, `b-${i}`)))] }));
|
|
218
|
+
return (_jsxs(Box, { flexDirection: "column", children: [avisos.map((a, i) => (_jsx(Text, { children: conCuerpo(a) }, `aviso-${i}`))), marco.lines.map((linea, i) => (_jsx(Text, { children: conCuerpo(linea) }, i)))] }));
|
|
244
219
|
}
|
|
@@ -1,2 +1,36 @@
|
|
|
1
1
|
export declare function montarArbol(): void;
|
|
2
|
+
/**
|
|
3
|
+
* Suelta el árbol.
|
|
4
|
+
*
|
|
5
|
+
* `darPorPintado()` ANTES de desmontar, y el orden importa: si el árbol se
|
|
6
|
+
* vuelve a montar —un diálogo que necesita el teclado para él solo—, el
|
|
7
|
+
* `<Static>` nuevo empieza su cuenta de cero y reimprimiría la conversación
|
|
8
|
+
* entera. En la pantalla normal esas líneas siguen ahí, en el terminal; la marca
|
|
9
|
+
* dice por dónde iba para que el montaje nuevo solo escriba lo que falta.
|
|
10
|
+
*
|
|
11
|
+
* `clear()` borra el marco de abajo antes de irse. Sin eso queda una copia
|
|
12
|
+
* muerta de la barra en mitad del historial, y en la próxima sesión de scroll el
|
|
13
|
+
* usuario se encuentra dos.
|
|
14
|
+
*/
|
|
2
15
|
export declare function desmontarArbol(): void;
|
|
16
|
+
export declare function hayArbol(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Borra la pantalla de verdad. Es lo que hacen /clear, /cd y Ctrl+L.
|
|
19
|
+
*
|
|
20
|
+
* POR QUE NO VALE UN `console.clear()`
|
|
21
|
+
*
|
|
22
|
+
* `console.clear()` escribe la secuencia de borrado directa al terminal, y no
|
|
23
|
+
* pasa por la interceptación de consola: ahí solo van `log`, `error`, `warn` e
|
|
24
|
+
* `info`. Con el árbol montado eso borra filas que Ink cree suyas, y a partir de
|
|
25
|
+
* ahí Ink borra y repinta sobre un sitio que ya no existe. Lo que se ve es
|
|
26
|
+
* basura, o nada.
|
|
27
|
+
*
|
|
28
|
+
* Aquí se hace en el orden que no rompe nada: se borra el marco con la cuenta
|
|
29
|
+
* todavía buena, se suelta el árbol, se limpia la pantalla —y el historial del
|
|
30
|
+
* terminal con `3J`, que es lo que el usuario espera de un /clear— y se vuelve a
|
|
31
|
+
* montar de cero.
|
|
32
|
+
*
|
|
33
|
+
* Y se vacía la transcripción: si no, el <Static> nuevo tendría por delante toda
|
|
34
|
+
* la conversación que se acaba de borrar.
|
|
35
|
+
*/
|
|
36
|
+
export declare function limpiarLaPantalla(): void;
|