chocolatito-code 1.6.6 → 1.6.8
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 +437 -341
- package/dist/agent/context.d.ts +6 -0
- package/dist/agent/context.js +69 -2
- package/dist/agent/loop.js +8 -0
- package/dist/agent/toolGate.js +13 -7
- package/dist/agent/verifier.js +2 -1
- package/dist/config/permissions.d.ts +17 -0
- package/dist/config/permissions.js +47 -7
- package/dist/config/plataforma.d.ts +62 -0
- package/dist/config/plataforma.js +109 -0
- package/dist/config/updater.js +4 -1
- package/dist/hooks/manager.js +2 -1
- package/dist/index.js +164 -39
- package/dist/memory/manager.d.ts +15 -5
- package/dist/memory/manager.js +146 -29
- package/dist/servidor/captura.d.ts +36 -0
- package/dist/servidor/captura.js +74 -0
- package/dist/servidor/pagina.d.ts +27 -0
- package/dist/servidor/pagina.js +268 -0
- package/dist/servidor/puente.d.ts +34 -0
- package/dist/servidor/puente.js +115 -0
- package/dist/servidor/servidor.d.ts +61 -0
- package/dist/servidor/servidor.js +249 -0
- package/dist/sessions/manager.d.ts +8 -0
- package/dist/sessions/manager.js +44 -0
- package/dist/sessions/resume.d.ts +12 -0
- package/dist/sessions/resume.js +10 -0
- package/dist/tools/backgroundTask.d.ts +64 -0
- package/dist/tools/backgroundTask.js +264 -0
- package/dist/tools/browserExtension.d.ts +2 -1
- package/dist/tools/browserExtension.js +48 -0
- package/dist/tools/computerUse.js +51 -9
- package/dist/tools/definitions.js +93 -1
- package/dist/tools/gitAudit.d.ts +41 -0
- package/dist/tools/gitAudit.js +282 -0
- package/dist/tools/runCommand.js +5 -2
- package/dist/tools/runner.js +36 -2
- package/dist/tools/safety.d.ts +1 -0
- package/dist/tools/safety.js +3 -0
- package/dist/tools/todoTool.d.ts +2 -0
- package/dist/tools/todoTool.js +29 -0
- package/dist/tools/toolDefsComputer.js +9 -0
- package/dist/tools/win/hostScript.js +7 -2
- package/dist/ui/comandos.js +2 -0
- 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 +3 -0
- package/dist/ui/marco.js +22 -3
- package/dist/ui/pantalla.d.ts +45 -17
- package/dist/ui/pantalla.js +150 -92
- package/dist/ui/permissionPrompt.d.ts +19 -17
- package/dist/ui/permissionPrompt.js +44 -1
- package/dist/ui/pieFijo.d.ts +1 -1
- package/dist/ui/pieFijo.js +27 -5
- package/dist/ui/renderer.js +8 -0
- package/dist/ui/selector.d.ts +6 -4
- package/dist/ui/selector.js +14 -1
- package/extension/background.js +80 -0
- package/extension/content.js +106 -0
- package/extension/manifest.json +3 -2
- package/package.json +3 -2
- package/dist/ui/historial.d.ts +0 -49
- package/dist/ui/historial.js +0 -92
package/dist/tools/runner.js
CHANGED
|
@@ -25,6 +25,7 @@ import { globalUndoManager } from "../agent/undoManager.js";
|
|
|
25
25
|
import { validateFileSyntax } from "../agent/syntaxValidator.js";
|
|
26
26
|
import { fileTracker } from "../agent/fileTracker.js";
|
|
27
27
|
import { mcpManager } from "../mcp/manager.js";
|
|
28
|
+
import { taskManager } from "./backgroundTask.js";
|
|
28
29
|
import fs from "node:fs";
|
|
29
30
|
import path from "node:path";
|
|
30
31
|
/**
|
|
@@ -82,10 +83,12 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
82
83
|
break;
|
|
83
84
|
}
|
|
84
85
|
case "save_memory":
|
|
85
|
-
|
|
86
|
+
memoryManager.setCwd(cwd);
|
|
87
|
+
rawResult = memoryManager.saveMemory(args.topic, args.fact, args.scope);
|
|
86
88
|
break;
|
|
87
89
|
case "read_memory":
|
|
88
|
-
|
|
90
|
+
memoryManager.setCwd(cwd);
|
|
91
|
+
rawResult = memoryManager.readMemory(args.topic, args.scope);
|
|
89
92
|
break;
|
|
90
93
|
case "spawn_agent": {
|
|
91
94
|
if (!apiKey) {
|
|
@@ -201,6 +204,37 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
201
204
|
case "run_command":
|
|
202
205
|
rawResult = await runCommand(args.command, cwd, args.timeout);
|
|
203
206
|
break;
|
|
207
|
+
case "start_background_task": {
|
|
208
|
+
const res = await taskManager.startTask(args.command, args.cwd || cwd, args.description);
|
|
209
|
+
rawResult = `Tarea iniciada en segundo plano:\n- ID: ${res.taskId}\n- PID: ${res.pid}\n- Estado: ${res.status}\n- Log: ${res.logPath}`;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "read_task_output": {
|
|
213
|
+
const res = taskManager.readTaskOutput(args.taskId, args.lines, args.offset);
|
|
214
|
+
if (res.status === "not_found") {
|
|
215
|
+
rawResult = res.output;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
rawResult = `[Tarea ${res.taskId}] Estado: ${res.status} (Exit Code: ${res.exitCode !== null ? res.exitCode : "N/A"})\nTotal de líneas: ${res.totalLines}\n--- Salida ---\n${res.output || "(sin salida aún)"}`;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "list_background_tasks": {
|
|
223
|
+
const tasks = taskManager.listTasks();
|
|
224
|
+
if (tasks.length === 0) {
|
|
225
|
+
rawResult = "No hay tareas en segundo plano registradas.";
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const lines = tasks.map((t) => `- [${t.taskId}] PID ${t.pid} | ${t.status.toUpperCase()} | Ejecución: ${t.runtimeSeconds}s | Comando: "${t.command}"${t.description ? ` (${t.description})` : ""}${t.exitCode !== null ? ` | Exit code: ${t.exitCode}` : ""}`);
|
|
229
|
+
rawResult = `Tareas en segundo plano (${tasks.length}):\n${lines.join("\n")}`;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "stop_background_task": {
|
|
234
|
+
const res = await taskManager.stopTask(args.taskId, args.force);
|
|
235
|
+
rawResult = res.message;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
204
238
|
case "list_dir":
|
|
205
239
|
rawResult = await listDir(args.dirPath || ".", cwd);
|
|
206
240
|
break;
|
package/dist/tools/safety.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* estado. En cambio dos escrituras seguidas SI pueden pisarse, y el orden
|
|
8
8
|
* importa, asi que esas van una detras de otra.
|
|
9
9
|
*/
|
|
10
|
+
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
10
11
|
export declare function isReadOnly(toolName: string): boolean;
|
|
11
12
|
/**
|
|
12
13
|
* Un lote solo se paraleliza si TODAS sus llamadas son de solo lectura y hay
|
package/dist/tools/safety.js
CHANGED
|
@@ -18,7 +18,10 @@ const READ_ONLY = new Set([
|
|
|
18
18
|
"web_search",
|
|
19
19
|
"read_memory",
|
|
20
20
|
"use_skill",
|
|
21
|
+
"read_task_output",
|
|
22
|
+
"list_background_tasks",
|
|
21
23
|
]);
|
|
24
|
+
export const READ_ONLY_TOOLS = READ_ONLY;
|
|
22
25
|
export function isReadOnly(toolName) {
|
|
23
26
|
return READ_ONLY.has(toolName);
|
|
24
27
|
}
|
package/dist/tools/todoTool.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export interface TaskItem {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function setSessionTasks(tasks: TaskItem[]): string;
|
|
7
7
|
export declare function getSessionTasks(): TaskItem[];
|
|
8
|
+
export declare function renderTaskWidget(tasks?: TaskItem[]): string | null;
|
|
9
|
+
export declare function sincronizarTareasConPie(tasks?: TaskItem[]): void;
|
|
8
10
|
export declare function renderTaskList(tasks: TaskItem[]): string;
|
package/dist/tools/todoTool.js
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { fijarSeccion, quitarSeccion } from "../ui/pieFijo.js";
|
|
2
3
|
let sessionTasks = [];
|
|
3
4
|
export function setSessionTasks(tasks) {
|
|
4
5
|
sessionTasks = tasks;
|
|
6
|
+
sincronizarTareasConPie(sessionTasks);
|
|
5
7
|
return renderTaskList(sessionTasks);
|
|
6
8
|
}
|
|
7
9
|
export function getSessionTasks() {
|
|
8
10
|
return sessionTasks;
|
|
9
11
|
}
|
|
12
|
+
export function renderTaskWidget(tasks) {
|
|
13
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
14
|
+
if (!list || list.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const total = list.length;
|
|
17
|
+
const completed = list.filter((t) => t.status === "completed").length;
|
|
18
|
+
const inProgress = list.find((t) => t.status === "in_progress");
|
|
19
|
+
return (chalk.hex("#D97757")("⎿") +
|
|
20
|
+
" " +
|
|
21
|
+
chalk.green("✔") +
|
|
22
|
+
` ${completed}/${total} tareas` +
|
|
23
|
+
(inProgress
|
|
24
|
+
? chalk.gray(" · ") + chalk.hex("#D97757")("En curso: ") + chalk.bold.white(inProgress.text)
|
|
25
|
+
: completed === total
|
|
26
|
+
? chalk.green(" · Completado")
|
|
27
|
+
: ""));
|
|
28
|
+
}
|
|
29
|
+
export function sincronizarTareasConPie(tasks) {
|
|
30
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
31
|
+
const widget = renderTaskWidget(list);
|
|
32
|
+
if (widget) {
|
|
33
|
+
fijarSeccion("tareas", [widget]);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
quitarSeccion("tareas");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
10
39
|
export function renderTaskList(tasks) {
|
|
11
40
|
if (tasks.length === 0)
|
|
12
41
|
return "No hay tareas registradas.";
|
|
@@ -111,6 +111,8 @@ export const COMPUTER_USE_TOOL = {
|
|
|
111
111
|
"scroll",
|
|
112
112
|
"type",
|
|
113
113
|
"key",
|
|
114
|
+
"hotkey",
|
|
115
|
+
"press",
|
|
114
116
|
"wait",
|
|
115
117
|
"wait_change",
|
|
116
118
|
"cursor_position",
|
|
@@ -183,11 +185,14 @@ export const CHROME_TOOL = {
|
|
|
183
185
|
"status", "tabs", "open", "select_tab", "navigate", "snapshot",
|
|
184
186
|
"click", "type", "press", "get_text", "eval", "wait_for",
|
|
185
187
|
"screenshot", "notice", "close_tab", "done",
|
|
188
|
+
"console_logs", "network_errors",
|
|
186
189
|
],
|
|
187
190
|
description: "status: comprueba que la extension esta conectada. tabs: lista las pestanas del usuario. " +
|
|
188
191
|
"open: abre una pestana nueva SIN quitarle la pantalla al usuario. select_tab: elige una ya abierta. " +
|
|
189
192
|
"snapshot: LEE la pagina y numera los elementos. click/type: actuan sobre un ref. " +
|
|
190
193
|
"wait_for: espera un texto o unos ms. screenshot: captura la pestana aunque no este visible y la describe. " +
|
|
194
|
+
"console_logs: obtiene errores y advertencias de consola y excepciones no controladas de la pagina. " +
|
|
195
|
+
"network_errors: obtiene peticiones de red HTTP fallidas (4xx/5xx o errores de conexion). " +
|
|
191
196
|
"notice: cambia el texto del aviso flotante. close_tab: cierra una pestaña que abriste. done: cierra el trabajo y quita el aviso.",
|
|
192
197
|
},
|
|
193
198
|
url: { type: "string", description: "URL para open o navigate." },
|
|
@@ -212,6 +217,10 @@ export const CHROME_TOOL = {
|
|
|
212
217
|
analyze: { type: "boolean", description: "false para no gastar tokens de vision." },
|
|
213
218
|
outputPath: { type: "string", description: "Ruta donde guardar la captura." },
|
|
214
219
|
note: { type: "string", description: "Texto inicial del aviso flotante al abrir la pestana." },
|
|
220
|
+
clear: {
|
|
221
|
+
type: "boolean",
|
|
222
|
+
description: "true para limpiar el buffer tras consultar (en console_logs o network_errors).",
|
|
223
|
+
},
|
|
215
224
|
},
|
|
216
225
|
required: ["action"],
|
|
217
226
|
},
|
|
@@ -283,9 +283,14 @@ public static class Choco
|
|
|
283
283
|
double scale = 1.0;
|
|
284
284
|
int longEdge = Math.Max(w, h);
|
|
285
285
|
if (maxLongEdge > 0 && longEdge > maxLongEdge) scale = (double)maxLongEdge / (double)longEdge;
|
|
286
|
+
ImageFormat fmt = ImageFormat.Png;
|
|
287
|
+
if (path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
|
|
288
|
+
{
|
|
289
|
+
fmt = ImageFormat.Jpeg;
|
|
290
|
+
}
|
|
286
291
|
if (scale >= 0.999)
|
|
287
292
|
{
|
|
288
|
-
bmp.Save(path,
|
|
293
|
+
bmp.Save(path, fmt);
|
|
289
294
|
return 1.0;
|
|
290
295
|
}
|
|
291
296
|
int nw = (int)Math.Round(w * scale), nh = (int)Math.Round(h * scale);
|
|
@@ -294,7 +299,7 @@ public static class Choco
|
|
|
294
299
|
{
|
|
295
300
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
296
301
|
g.DrawImage(bmp, 0, 0, nw, nh);
|
|
297
|
-
dst.Save(path,
|
|
302
|
+
dst.Save(path, fmt);
|
|
298
303
|
}
|
|
299
304
|
return scale;
|
|
300
305
|
}
|
package/dist/ui/comandos.js
CHANGED
|
@@ -35,6 +35,8 @@ export const SLASH_COMMANDS = [
|
|
|
35
35
|
{ name: "/goal", description: "Ejecutar meta compleja autónoma con auto-corrección", args: "<meta>" },
|
|
36
36
|
{ name: "/compact", description: "Comprimir contexto y optimizar memoria de la sesión" },
|
|
37
37
|
{ name: "/diff", description: "Ver diferencias de código (Git diff) del proyecto" },
|
|
38
|
+
{ name: "/audit", description: "Auditar git diff en busca de secretos, logs residuales y sugerir commit", args: "[review]" },
|
|
39
|
+
{ name: "/review", description: "Revisar cambios pendientes de git (alias de /audit)" },
|
|
38
40
|
{ name: "/commit", description: "Crear un commit en Git con mensaje semántico", args: "<mensaje>" },
|
|
39
41
|
{ name: "/cd", description: "Cambiar el directorio de trabajo activo", args: "<ruta>" },
|
|
40
42
|
{ name: "/uso", description: "Ver cuánto llevas consumido de tu cuenta" },
|
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;
|