chocolatito-code 1.0.0 → 1.2.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 +155 -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 +642 -91
- 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/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +53 -0
- 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/nodeVersion.d.ts +13 -0
- package/dist/config/nodeVersion.js +26 -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 +240 -37
- package/dist/mcp/client.d.ts +50 -0
- package/dist/mcp/client.js +155 -19
- package/dist/mcp/ide.d.ts +76 -0
- package/dist/mcp/ide.js +160 -0
- package/dist/mcp/manager.js +9 -0
- 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 +237 -22
- 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 +17 -4
- 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/grepSearch.js +111 -23
- 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 +58 -5
- 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.d.ts +16 -1
- package/dist/tools/writeFile.js +40 -2
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +59 -7
- package/dist/ui/keyboardGuard.d.ts +76 -0
- package/dist/ui/keyboardGuard.js +94 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/pegados.d.ts +50 -0
- package/dist/ui/pegados.js +83 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +40 -0
- package/dist/ui/prompt.js +436 -131
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/extension/README.md +95 -74
- package/extension/background.js +772 -181
- package/extension/content.js +527 -46
- package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
- package/extension/iconos/128.png +0 -0
- package/extension/iconos/16.png +0 -0
- package/extension/iconos/32.png +0 -0
- package/extension/iconos/48.png +0 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +28 -4
- package/extension/popup.html +16 -6
- package/extension/popup.js +28 -12
- package/package.json +3 -2
package/dist/tools/runner.js
CHANGED
|
@@ -27,7 +27,34 @@ import { fileTracker } from "../agent/fileTracker.js";
|
|
|
27
27
|
import { mcpManager } from "../mcp/manager.js";
|
|
28
28
|
import fs from "node:fs";
|
|
29
29
|
import path from "node:path";
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Un SkillManager por directorio de trabajo.
|
|
32
|
+
*
|
|
33
|
+
* Antes habia una unica instancia de modulo creada con `new SkillManager()`, y
|
|
34
|
+
* eso rompia use_skill de dos formas a la vez:
|
|
35
|
+
*
|
|
36
|
+
* 1. El constructor solo arma la lista de directorios; no escanea. Como nadie
|
|
37
|
+
* llamaba a scanSkills() sobre ESTA instancia (la que escanea es la de
|
|
38
|
+
* index.ts, que es otro objeto con otro Map), la primera use_skill de cada
|
|
39
|
+
* proceso caia en la rama de error y devolvia "no se encontro el skill X"
|
|
40
|
+
* seguido de una lista donde X aparecia. Solo acertaba al segundo intento,
|
|
41
|
+
* porque la propia rama de error llama a getSkillPromptListing() y esa si
|
|
42
|
+
* escanea.
|
|
43
|
+
*
|
|
44
|
+
* 2. Se construia con process.cwd() capturado al importar el modulo, asi que
|
|
45
|
+
* los skills de proyecto (./skills y ./.skills) se resolvian siempre contra
|
|
46
|
+
* el directorio de lanzamiento y nunca seguian a un change_dir.
|
|
47
|
+
*/
|
|
48
|
+
const skillManagers = new Map();
|
|
49
|
+
function getSkillManager(cwd) {
|
|
50
|
+
const key = path.resolve(cwd);
|
|
51
|
+
let manager = skillManagers.get(key);
|
|
52
|
+
if (!manager) {
|
|
53
|
+
manager = new SkillManager(key);
|
|
54
|
+
skillManagers.set(key, manager);
|
|
55
|
+
}
|
|
56
|
+
return manager;
|
|
57
|
+
}
|
|
31
58
|
const memoryManager = new MemoryManager();
|
|
32
59
|
export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, modelName) {
|
|
33
60
|
try {
|
|
@@ -44,6 +71,7 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
44
71
|
rawResult = setSessionTasks(args.tasks || []);
|
|
45
72
|
break;
|
|
46
73
|
case "use_skill": {
|
|
74
|
+
const skillManager = getSkillManager(cwd);
|
|
47
75
|
const body = skillManager.loadSkillBody(args.skillName);
|
|
48
76
|
if (body) {
|
|
49
77
|
rawResult = `--- INSTRUCCIONES DEL SKILL "${args.skillName}" ---\n${body}\n--- FIN DEL SKILL ---`;
|
|
@@ -121,8 +149,22 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
121
149
|
break;
|
|
122
150
|
}
|
|
123
151
|
case "write_file": {
|
|
152
|
+
// Sobreescribir un archivo que ya existe es la edicion mas destructiva
|
|
153
|
+
// que hay: se lleva por delante TODO el contenido anterior. Aun asi era
|
|
154
|
+
// el unico camino de escritura que no pasaba por el fileTracker, con lo
|
|
155
|
+
// que la ruta barata para el modelo era justo la insegura: si edit_file
|
|
156
|
+
// le bloqueaba por no haber leido el archivo, bastaba con reescribirlo
|
|
157
|
+
// entero para saltarse la comprobacion.
|
|
158
|
+
//
|
|
159
|
+
// checkBeforeEdit devuelve null cuando el archivo no existe, asi que
|
|
160
|
+
// crear uno nuevo sigue sin pedir permiso a nadie.
|
|
161
|
+
const risk = fileTracker.checkBeforeEdit(args.path, cwd);
|
|
162
|
+
if (risk) {
|
|
163
|
+
rawResult = `Escritura bloqueada: ${risk}`;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
124
166
|
globalUndoManager.snapshotFile(args.path, cwd);
|
|
125
|
-
rawResult = await writeFile(args.path, args.content, cwd);
|
|
167
|
+
rawResult = await writeFile(args.path, args.content, cwd, args.modo === "anadir" ? "anadir" : "sobrescribir");
|
|
126
168
|
fileTracker.markWritten(args.path, cwd);
|
|
127
169
|
const check = validateFileSyntax(args.path, args.content || "");
|
|
128
170
|
if (!check.valid && check.warning) {
|
|
@@ -131,19 +173,30 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
131
173
|
break;
|
|
132
174
|
}
|
|
133
175
|
case "create_image":
|
|
176
|
+
// Era la unica herramienta con efecto en disco sin snapshot: si el
|
|
177
|
+
// generador pisaba un archivo que ya existia, /undo no lo recuperaba.
|
|
178
|
+
// Y /undo es justo lo que justifica que las escrituras no pregunten.
|
|
179
|
+
globalUndoManager.snapshotFile(args.outputPath, cwd);
|
|
134
180
|
rawResult = await createImage(args.outputPath, args.generatorScript, cwd);
|
|
135
181
|
break;
|
|
136
182
|
case "web_search":
|
|
137
183
|
rawResult = await webSearch(args.query);
|
|
138
184
|
break;
|
|
139
|
-
case "delete_file":
|
|
140
|
-
|
|
185
|
+
case "delete_file": {
|
|
186
|
+
// El snapshot dice si hay copia. Sobre un directorio (o un archivo
|
|
187
|
+
// bloqueado) no la hay, y el borrado es definitivo: se avisa aqui en vez
|
|
188
|
+
// de dejar que /undo finja despues que lo ha devuelto.
|
|
189
|
+
const copia = globalUndoManager.snapshotFile(args.path, cwd);
|
|
141
190
|
rawResult = await deleteFile(args.path, cwd);
|
|
191
|
+
if (copia === "unrestorable" && !rawResult.startsWith("Error")) {
|
|
192
|
+
rawResult += "\n⚠ Sin copia de seguridad: /undo no puede revertir este borrado.";
|
|
193
|
+
}
|
|
142
194
|
break;
|
|
195
|
+
}
|
|
143
196
|
case "move_copy_file":
|
|
144
197
|
globalUndoManager.snapshotFile(args.sourcePath, cwd);
|
|
145
198
|
globalUndoManager.snapshotFile(args.destinationPath, cwd);
|
|
146
|
-
rawResult = await moveCopyFile(args.sourcePath, args.destinationPath, args.operation || "move", cwd);
|
|
199
|
+
rawResult = await moveCopyFile(args.sourcePath, args.destinationPath, args.operation || "move", cwd, args.overwrite === true);
|
|
147
200
|
break;
|
|
148
201
|
case "run_command":
|
|
149
202
|
rawResult = await runCommand(args.command, cwd, args.timeout);
|
|
@@ -141,7 +141,12 @@ export const COMPUTER_USE_TOOL = {
|
|
|
141
141
|
y: { type: "integer", description: "Coordenada Y en pixeles fisicos." },
|
|
142
142
|
start_coordinate: { type: "array", items: { type: "integer" }, description: "[x, y] de inicio del arrastre." },
|
|
143
143
|
text: { type: "string", description: "Texto a escribir." },
|
|
144
|
-
keys: {
|
|
144
|
+
keys: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: 'Tecla o atajo (ej. "Return", "Escape", "ctrl+s", "alt+Tab"). ' +
|
|
147
|
+
"Un atajo no se puede mandar a una ventana de fondo: si pasas window, esa ventana se trae al frente antes de teclear. " +
|
|
148
|
+
"Sin window va a lo que el usuario tenga delante.",
|
|
149
|
+
},
|
|
145
150
|
repeat: { type: "integer", description: "Veces que repetir la tecla." },
|
|
146
151
|
direction: { type: "string", enum: ["up", "down", "left", "right"], description: "Direccion del scroll." },
|
|
147
152
|
amount: { type: "integer", description: "Cantidad de scroll." },
|
|
@@ -182,7 +187,12 @@ export const CHROME_TOOL = {
|
|
|
182
187
|
"notice: cambia el texto del aviso flotante. close_tab: cierra una pestaña que abriste. done: cierra el trabajo y quita el aviso.",
|
|
183
188
|
},
|
|
184
189
|
url: { type: "string", description: "URL para open o navigate." },
|
|
185
|
-
tabId: {
|
|
190
|
+
tabId: {
|
|
191
|
+
type: "integer",
|
|
192
|
+
description: "Pestana concreta; por defecto, en la que se esta trabajando. " +
|
|
193
|
+
"Si no hay ninguna (aun no has hecho open/select_tab, o ya llamaste a done), " +
|
|
194
|
+
"navigate, close_tab, press y eval fallan a proposito: no se actua sobre la pestana del usuario.",
|
|
195
|
+
},
|
|
186
196
|
ref: { type: "integer", description: "Numero de elemento devuelto por snapshot." },
|
|
187
197
|
text: { type: "string", description: "Texto a escribir (type) o a esperar (wait_for)." },
|
|
188
198
|
submit: { type: "boolean", description: "true para pulsar Enter despues de escribir." },
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Topes de una salida de herramienta antes de recortarla.
|
|
3
|
+
*
|
|
4
|
+
* El limite estaba en 60.000 caracteres, que es entre dos y cuatro veces lo que
|
|
5
|
+
* usan los demas agentes (opencode 50 KB, Cline 48 K, Crush 30 K). El motivo
|
|
6
|
+
* por el que importa lo explica bien un comentario de Cline: cada caracter que
|
|
7
|
+
* devuelve una herramienta se REENVIA al modelo en todas las peticiones
|
|
8
|
+
* posteriores del turno, y el bucle puede dar treinta vueltas. Una salida
|
|
9
|
+
* gorda no cuesta lo que ocupa: cuesta lo que ocupa multiplicado por lo que
|
|
10
|
+
* queda de turno.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_TOOL_OUTPUT_CHARS = 24000;
|
|
13
|
+
/** Un fichero minificado de una sola linea pasaba entero hasta el tope global. */
|
|
14
|
+
export declare const MAX_LINES = 2000;
|
|
15
|
+
export declare const MAX_LINE_CHARS = 2000;
|
|
16
|
+
/**
|
|
17
|
+
* Guarda la salida entera en disco y devuelve la ruta, o null si no se pudo.
|
|
18
|
+
*
|
|
19
|
+
* Lo omitido se perdia para siempre: si el trozo que importaba estaba en el
|
|
20
|
+
* medio, el agente no tenia forma de recuperarlo y acababa relanzando el mismo
|
|
21
|
+
* comando —y volviendo a truncarlo igual—. Guardarlo cuesta un fichero y
|
|
22
|
+
* convierte "se perdio" en "esta ahi, ve a buscarlo".
|
|
23
|
+
*/
|
|
24
|
+
export declare function volcarSalida(contenido: string): string | null;
|
|
2
25
|
/**
|
|
3
26
|
* Recorta la salida de una herramienta conservando el PRINCIPIO Y EL FINAL.
|
|
4
27
|
*
|
|
@@ -7,5 +30,8 @@ export declare const MAX_TOOL_OUTPUT_CHARS = 60000;
|
|
|
7
30
|
* resumen de los tests y el codigo de salida estan al final. El agente veia
|
|
8
31
|
* 25.000 caracteres de compilacion y se perdia la linea que decia por que habia
|
|
9
32
|
* fallado.
|
|
33
|
+
*
|
|
34
|
+
* Ahora, ademas, lo que no cabe no se tira: se escribe entero a disco y se le
|
|
35
|
+
* dice al agente como llegar a ello sin quemarse el contexto.
|
|
10
36
|
*/
|
|
11
37
|
export declare function truncateOutput(output: string, maxChars?: number): string;
|
package/dist/tools/truncator.js
CHANGED
|
@@ -1,4 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* Topes de una salida de herramienta antes de recortarla.
|
|
7
|
+
*
|
|
8
|
+
* El limite estaba en 60.000 caracteres, que es entre dos y cuatro veces lo que
|
|
9
|
+
* usan los demas agentes (opencode 50 KB, Cline 48 K, Crush 30 K). El motivo
|
|
10
|
+
* por el que importa lo explica bien un comentario de Cline: cada caracter que
|
|
11
|
+
* devuelve una herramienta se REENVIA al modelo en todas las peticiones
|
|
12
|
+
* posteriores del turno, y el bucle puede dar treinta vueltas. Una salida
|
|
13
|
+
* gorda no cuesta lo que ocupa: cuesta lo que ocupa multiplicado por lo que
|
|
14
|
+
* queda de turno.
|
|
15
|
+
*/
|
|
16
|
+
export const MAX_TOOL_OUTPUT_CHARS = 24_000;
|
|
17
|
+
/** Un fichero minificado de una sola linea pasaba entero hasta el tope global. */
|
|
18
|
+
export const MAX_LINES = 2_000;
|
|
19
|
+
export const MAX_LINE_CHARS = 2_000;
|
|
20
|
+
/** Dias que se conservan los volcados antes de barrerlos. */
|
|
21
|
+
const RETENCION_DIAS = 7;
|
|
22
|
+
function directorioDeVolcados() {
|
|
23
|
+
return path.join(os.homedir(), ".chocolatito", "outputs");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Borra los volcados viejos. Se llama al escribir uno nuevo: es el unico
|
|
27
|
+
* momento en que se sabe que el directorio existe y que a alguien le importa.
|
|
28
|
+
*/
|
|
29
|
+
function barrerVolcadosViejos(dir) {
|
|
30
|
+
const limite = Date.now() - RETENCION_DIAS * 24 * 60 * 60 * 1000;
|
|
31
|
+
try {
|
|
32
|
+
for (const nombre of fs.readdirSync(dir)) {
|
|
33
|
+
const ruta = path.join(dir, nombre);
|
|
34
|
+
try {
|
|
35
|
+
if (fs.statSync(ruta).mtimeMs < limite)
|
|
36
|
+
fs.unlinkSync(ruta);
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Guarda la salida entera en disco y devuelve la ruta, o null si no se pudo.
|
|
45
|
+
*
|
|
46
|
+
* Lo omitido se perdia para siempre: si el trozo que importaba estaba en el
|
|
47
|
+
* medio, el agente no tenia forma de recuperarlo y acababa relanzando el mismo
|
|
48
|
+
* comando —y volviendo a truncarlo igual—. Guardarlo cuesta un fichero y
|
|
49
|
+
* convierte "se perdio" en "esta ahi, ve a buscarlo".
|
|
50
|
+
*/
|
|
51
|
+
export function volcarSalida(contenido) {
|
|
52
|
+
try {
|
|
53
|
+
const dir = directorioDeVolcados();
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
55
|
+
barrerVolcadosViejos(dir);
|
|
56
|
+
const nombre = `${Date.now()}-${crypto.randomBytes(4).toString("hex")}.txt`;
|
|
57
|
+
const ruta = path.join(dir, nombre);
|
|
58
|
+
fs.writeFileSync(ruta, contenido, "utf-8");
|
|
59
|
+
return ruta;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Recorta las lineas absurdamente largas, avisando de cuanto se quito. */
|
|
66
|
+
function recortarLineasLargas(salida) {
|
|
67
|
+
if (!salida.includes("\n") && salida.length <= MAX_LINE_CHARS)
|
|
68
|
+
return salida;
|
|
69
|
+
let tocada = false;
|
|
70
|
+
const lineas = salida.split("\n").map((linea) => {
|
|
71
|
+
if (linea.length <= MAX_LINE_CHARS)
|
|
72
|
+
return linea;
|
|
73
|
+
tocada = true;
|
|
74
|
+
return `${linea.slice(0, MAX_LINE_CHARS)}… [+${(linea.length - MAX_LINE_CHARS).toLocaleString()} caracteres en esta linea]`;
|
|
75
|
+
});
|
|
76
|
+
return tocada ? lineas.join("\n") : salida;
|
|
77
|
+
}
|
|
2
78
|
/**
|
|
3
79
|
* Recorta la salida de una herramienta conservando el PRINCIPIO Y EL FINAL.
|
|
4
80
|
*
|
|
@@ -7,18 +83,48 @@ export const MAX_TOOL_OUTPUT_CHARS = 60_000;
|
|
|
7
83
|
* resumen de los tests y el codigo de salida estan al final. El agente veia
|
|
8
84
|
* 25.000 caracteres de compilacion y se perdia la linea que decia por que habia
|
|
9
85
|
* fallado.
|
|
86
|
+
*
|
|
87
|
+
* Ahora, ademas, lo que no cabe no se tira: se escribe entero a disco y se le
|
|
88
|
+
* dice al agente como llegar a ello sin quemarse el contexto.
|
|
10
89
|
*/
|
|
11
90
|
export function truncateOutput(output, maxChars = MAX_TOOL_OUTPUT_CHARS) {
|
|
12
|
-
if (!output
|
|
91
|
+
if (!output)
|
|
13
92
|
return output;
|
|
93
|
+
const completo = output;
|
|
94
|
+
let trabajo = recortarLineasLargas(output);
|
|
95
|
+
const lineas = trabajo.split("\n");
|
|
96
|
+
const sobranLineas = lineas.length > MAX_LINES;
|
|
97
|
+
if (sobranLineas) {
|
|
98
|
+
const cabeza = Math.floor(MAX_LINES * 0.65);
|
|
99
|
+
const cola = MAX_LINES - cabeza;
|
|
100
|
+
trabajo =
|
|
101
|
+
lineas.slice(0, cabeza).join("\n") +
|
|
102
|
+
`\n\n[... ${(lineas.length - MAX_LINES).toLocaleString()} lineas omitidas del medio ...]\n\n` +
|
|
103
|
+
lineas.slice(lineas.length - cola).join("\n");
|
|
104
|
+
}
|
|
105
|
+
if (trabajo.length <= maxChars && !sobranLineas && trabajo === completo) {
|
|
106
|
+
return completo;
|
|
107
|
+
}
|
|
108
|
+
let recortado = trabajo;
|
|
109
|
+
let omitidos = 0;
|
|
110
|
+
if (trabajo.length > maxChars) {
|
|
111
|
+
// Dos tercios al principio (contexto de lo que se hizo) y un tercio al final
|
|
112
|
+
// (donde suele estar el desenlace).
|
|
113
|
+
const headChars = Math.floor(maxChars * 0.65);
|
|
114
|
+
const tailChars = maxChars - headChars;
|
|
115
|
+
omitidos = trabajo.length - maxChars;
|
|
116
|
+
recortado =
|
|
117
|
+
trabajo.slice(0, headChars) +
|
|
118
|
+
`\n\n[... ${omitidos.toLocaleString()} caracteres omitidos del medio ` +
|
|
119
|
+
`para proteger el contexto; se conserva el final ...]\n\n` +
|
|
120
|
+
trabajo.slice(trabajo.length - tailChars);
|
|
14
121
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`para proteger el contexto; se conserva el final ...]\n\n${tail}`);
|
|
122
|
+
const ruta = volcarSalida(completo);
|
|
123
|
+
if (!ruta)
|
|
124
|
+
return recortado;
|
|
125
|
+
return (`${recortado}\n\n` +
|
|
126
|
+
`[Salida completa (${completo.length.toLocaleString()} caracteres) guardada en: ${ruta}\n` +
|
|
127
|
+
` Para consultarla sin gastar contexto: grep_search sobre esa ruta, o view_file con startLine/endLine.\n` +
|
|
128
|
+
` Si hay que revisarla entera, manda un spawn_agent que la lea y te resuma lo que buscas.\n` +
|
|
129
|
+
` No la leas entera tu: volveria a ocupar lo mismo que se acaba de recortar.]`);
|
|
24
130
|
}
|
package/dist/tools/viewFile.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { binaryExtensionLabel, looksBinary } from "./binary.js";
|
|
3
4
|
export async function viewFile(filePath, startLine, endLine, cwd = process.cwd()) {
|
|
4
5
|
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
5
6
|
if (!fs.existsSync(resolved)) {
|
|
@@ -9,18 +10,21 @@ export async function viewFile(filePath, startLine, endLine, cwd = process.cwd()
|
|
|
9
10
|
if (stat.isDirectory()) {
|
|
10
11
|
return `Error: "${filePath}" es un directorio, usa list_dir en su lugar.`;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const ext = path.extname(resolved).toLowerCase();
|
|
20
|
-
if (BINARY_EXTENSIONS.has(ext)) {
|
|
21
|
-
return `Aviso: "${filePath}" es un archivo binario (${ext.slice(1).toUpperCase()}) y no contiene texto plano legible. No intentes leerlo como texto.`;
|
|
13
|
+
// Esto empezaba por "Aviso:", y runner.ts solo se salta el markRead cuando el
|
|
14
|
+
// resultado empieza por "Error". O sea que un binario "visto" quedaba marcado
|
|
15
|
+
// como leido y el fileTracker dejaba pasar el edit_file que lo destruia. Es un
|
|
16
|
+
// error, no un aviso.
|
|
17
|
+
const etiqueta = binaryExtensionLabel(resolved);
|
|
18
|
+
if (etiqueta) {
|
|
19
|
+
return `Error: "${filePath}" es un archivo binario (${etiqueta}) y no contiene texto plano legible. No intentes leerlo ni editarlo como texto.`;
|
|
22
20
|
}
|
|
23
|
-
const
|
|
21
|
+
const buffer = fs.readFileSync(resolved);
|
|
22
|
+
// La lista de extensiones no cubre lo que no tiene extension o la tiene rara
|
|
23
|
+
// (un .dat, un volcado sin nombre). Un byte nulo lo delata igual.
|
|
24
|
+
if (looksBinary(buffer)) {
|
|
25
|
+
return `Error: "${filePath}" contiene bytes nulos: es binario, no texto. No intentes leerlo ni editarlo como texto.`;
|
|
26
|
+
}
|
|
27
|
+
const content = buffer.toString("utf-8");
|
|
24
28
|
const lines = content.split(/\r?\n/);
|
|
25
29
|
const totalLines = lines.length;
|
|
26
30
|
const isUnbounded = !startLine && !endLine;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { VISION_MODEL } from "../config/constants.js";
|
|
3
3
|
import { createEngineClient } from "../config/engine.js";
|
|
4
|
+
import { apuntarUso } from "../agent/usageMeter.js";
|
|
4
5
|
function client(apiKey) {
|
|
5
6
|
return createEngineClient(apiKey, "vision");
|
|
6
7
|
}
|
|
@@ -28,6 +29,12 @@ async function ask(apiKey, prompt, imagePath, maxTokens) {
|
|
|
28
29
|
},
|
|
29
30
|
],
|
|
30
31
|
});
|
|
32
|
+
// Se apunta aqui, no en quien llama: `usage` se venia devolviendo en el
|
|
33
|
+
// resultado y no lo leia nadie (computerUse, browserCdp y browserExtension
|
|
34
|
+
// solo usan el texto), asi que cada captura descrita durante el control del
|
|
35
|
+
// ordenador —y son muchas, con una imagen entera en el prompt— costaba
|
|
36
|
+
// dinero y sumaba cero. Contarlo donde se gasta evita repetir el olvido.
|
|
37
|
+
apuntarUso(res.usage, VISION_MODEL, "vision");
|
|
31
38
|
const msg = res.choices[0]?.message;
|
|
32
39
|
// El modelo de vision razona antes de responder. Si el presupuesto de tokens
|
|
33
40
|
// se agota razonando, 'content' llega vacio y el agente se queda sin ojos sin
|
|
@@ -1 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Busqueda web contra el HTML de DuckDuckGo.
|
|
3
|
+
*
|
|
4
|
+
* Lo que devuelve esto se lo traga el modelo y casi siempre lo encadena con
|
|
5
|
+
* web_fetch ("busca X y leelo"), asi que cada URL tiene que poder abrirse tal
|
|
6
|
+
* cual sale de aqui. Antes no se podia ninguna: ver resolverEnlace().
|
|
7
|
+
*/
|
|
8
|
+
interface ResultadoWeb {
|
|
9
|
+
titulo: string;
|
|
10
|
+
url: string;
|
|
11
|
+
resumen: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Separado de la peticion para poder probarlo sin red.
|
|
15
|
+
*
|
|
16
|
+
* El titulo sale de result__a. La version anterior leia el href y el texto de
|
|
17
|
+
* result__url, que no es el titulo sino el dominio de adorno
|
|
18
|
+
* ("www.speedtest.net"): el modelo nunca llego a ver el titulo de la pagina.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parsearResultados(html: string, maximo?: number): ResultadoWeb[];
|
|
1
21
|
export declare function webSearch(query: string): Promise<string>;
|
|
22
|
+
export {};
|
package/dist/tools/webSearch.js
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Busqueda web contra el HTML de DuckDuckGo.
|
|
3
|
+
*
|
|
4
|
+
* Lo que devuelve esto se lo traga el modelo y casi siempre lo encadena con
|
|
5
|
+
* web_fetch ("busca X y leelo"), asi que cada URL tiene que poder abrirse tal
|
|
6
|
+
* cual sale de aqui. Antes no se podia ninguna: ver resolverEnlace().
|
|
7
|
+
*/
|
|
8
|
+
const ENTIDADES_NOMBRADAS = {
|
|
9
|
+
amp: "&",
|
|
10
|
+
lt: "<",
|
|
11
|
+
gt: ">",
|
|
12
|
+
quot: '"',
|
|
13
|
+
apos: "'",
|
|
14
|
+
nbsp: " ",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Una sola pasada a proposito: si se decodificara "&" antes que el resto, un
|
|
18
|
+
* "&#x27;" literal acabaria convertido en apostrofe, que no es lo que decia
|
|
19
|
+
* el HTML.
|
|
20
|
+
*/
|
|
21
|
+
function decodificarEntidades(texto) {
|
|
22
|
+
return texto.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (completo, entidad) => {
|
|
23
|
+
const clave = entidad.toLowerCase();
|
|
24
|
+
if (clave.startsWith("#")) {
|
|
25
|
+
const codigo = clave.startsWith("#x") ? parseInt(clave.slice(2), 16) : Number(clave.slice(1));
|
|
26
|
+
if (!Number.isFinite(codigo) || codigo < 0 || codigo > 0x10ffff)
|
|
27
|
+
return completo;
|
|
28
|
+
return String.fromCodePoint(codigo);
|
|
29
|
+
}
|
|
30
|
+
return clave in ENTIDADES_NOMBRADAS ? ENTIDADES_NOMBRADAS[clave] : completo;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Etiquetas fuera primero y entidades despues. Al reves, un "<b>" del
|
|
35
|
+
* texto se decodificaria a "<b>" y el limpiador se lo comeria como si fuera
|
|
36
|
+
* marcado de verdad.
|
|
37
|
+
*/
|
|
38
|
+
function aTextoPlano(fragmento) {
|
|
39
|
+
return decodificarEntidades(fragmento.replace(/<[^>]+>/g, " "))
|
|
40
|
+
.replace(/\s+/g, " ")
|
|
41
|
+
.trim();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* En html.duckduckgo.com ningun href apunta al destino: todos van al redirector
|
|
45
|
+
* de DDG y ademas sin esquema ("//duckduckgo.com/l/?uddg=...&rut=..."). Eso
|
|
46
|
+
* es inservible para web_fetch por partida doble: fetch() no sabe parsear una
|
|
47
|
+
* URL protocol-relative ("Failed to parse URL from //duckduckgo.com/...") y el
|
|
48
|
+
* redirector, aunque se le ponga el "https:" delante, contesta HTTP 400 a quien
|
|
49
|
+
* no llegue desde el navegador. El destino real esta en el parametro uddg.
|
|
50
|
+
*/
|
|
51
|
+
function resolverEnlace(href) {
|
|
52
|
+
// El href viene escapado dentro del atributo ("&rut="); sin desescapar,
|
|
53
|
+
// el separador de parametros no es un "&" y uddg se lleva el rut pegado.
|
|
54
|
+
const limpio = decodificarEntidades(href.trim());
|
|
55
|
+
if (!limpio)
|
|
56
|
+
return null;
|
|
57
|
+
let redirector;
|
|
58
|
+
try {
|
|
59
|
+
redirector = new URL(limpio, "https://html.duckduckgo.com/");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// searchParams ya hace el decodeURIComponent; repetirlo a mano romperia
|
|
65
|
+
// cualquier destino con un %25 legitimo.
|
|
66
|
+
const destinoCrudo = redirector.searchParams.get("uddg") ?? redirector.href;
|
|
67
|
+
let destino;
|
|
68
|
+
try {
|
|
69
|
+
destino = new URL(destinoCrudo);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (destino.protocol !== "https:" && destino.protocol !== "http:")
|
|
75
|
+
return null;
|
|
76
|
+
// Los primeros huecos de DDG son anuncios, y su uddg no lleva a la pagina
|
|
77
|
+
// anunciada sino al contador de clics de DDG (/y.js?ad_domain=...). Sin este
|
|
78
|
+
// filtro, el resultado [1] que ve el modelo es publicidad que encima no se
|
|
79
|
+
// puede abrir.
|
|
80
|
+
if (destino.hostname.endsWith("duckduckgo.com") && destino.pathname === "/y.js")
|
|
81
|
+
return null;
|
|
82
|
+
return destino.href;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Separado de la peticion para poder probarlo sin red.
|
|
86
|
+
*
|
|
87
|
+
* El titulo sale de result__a. La version anterior leia el href y el texto de
|
|
88
|
+
* result__url, que no es el titulo sino el dominio de adorno
|
|
89
|
+
* ("www.speedtest.net"): el modelo nunca llego a ver el titulo de la pagina.
|
|
90
|
+
*/
|
|
91
|
+
export function parsearResultados(html, maximo = 6) {
|
|
92
|
+
const anclaTitulo = /<a\b([^>]*\bclass="[^"]*\bresult__a\b[^"]*"[^>]*)>([\s\S]*?)<\/a>/g;
|
|
93
|
+
const anclas = [];
|
|
94
|
+
let m;
|
|
95
|
+
while ((m = anclaTitulo.exec(html)) !== null) {
|
|
96
|
+
anclas.push({ atributos: m[1], titulo: m[2], inicio: m.index, fin: anclaTitulo.lastIndex });
|
|
97
|
+
}
|
|
98
|
+
const resultados = [];
|
|
99
|
+
for (let i = 0; i < anclas.length && resultados.length < maximo; i++) {
|
|
100
|
+
const ancla = anclas[i];
|
|
101
|
+
// El orden de los atributos no esta garantizado, asi que el href se saca
|
|
102
|
+
// aparte en vez de encadenarlo a la clase dentro de la misma regex.
|
|
103
|
+
const href = /\bhref="([^"]*)"/.exec(ancla.atributos)?.[1];
|
|
104
|
+
if (!href)
|
|
105
|
+
continue;
|
|
106
|
+
const url = resolverEnlace(href);
|
|
107
|
+
if (!url)
|
|
108
|
+
continue;
|
|
109
|
+
const titulo = aTextoPlano(ancla.titulo);
|
|
110
|
+
if (!titulo)
|
|
111
|
+
continue;
|
|
112
|
+
// El resumen se busca solo hasta donde empieza el siguiente resultado: si
|
|
113
|
+
// uno viene sin snippet, una busqueda perezosa sin tope le robaria el del
|
|
114
|
+
// siguiente y emparejaria titulo y texto de paginas distintas.
|
|
115
|
+
const hasta = i + 1 < anclas.length ? anclas[i + 1].inicio : html.length;
|
|
116
|
+
const trozo = html.slice(ancla.fin, hasta);
|
|
117
|
+
const resumen = /<a\b[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/a>/.exec(trozo)?.[1] ?? "";
|
|
118
|
+
resultados.push({ titulo, url, resumen: aTextoPlano(resumen) });
|
|
119
|
+
}
|
|
120
|
+
return resultados;
|
|
121
|
+
}
|
|
1
122
|
export async function webSearch(query) {
|
|
2
123
|
try {
|
|
3
124
|
const encoded = encodeURIComponent(query);
|
|
@@ -12,22 +133,12 @@ export async function webSearch(query) {
|
|
|
12
133
|
return `Error al buscar en la web: HTTP ${res.status}`;
|
|
13
134
|
}
|
|
14
135
|
const html = await res.text();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const regex = /<a class="result__url"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
18
|
-
let match;
|
|
19
|
-
let count = 0;
|
|
20
|
-
while ((match = regex.exec(html)) !== null && count < 6) {
|
|
21
|
-
count++;
|
|
22
|
-
const link = match[1].trim();
|
|
23
|
-
const title = match[2].replace(/<[^>]+>/g, "").trim();
|
|
24
|
-
const snippet = match[3].replace(/<[^>]+>/g, "").trim();
|
|
25
|
-
results.push(`[${count}] ${title}\n URL: ${link}\n ${snippet}`);
|
|
26
|
-
}
|
|
27
|
-
if (results.length === 0) {
|
|
136
|
+
const resultados = parsearResultados(html);
|
|
137
|
+
if (resultados.length === 0) {
|
|
28
138
|
return `No se encontraron resultados directos para "${query}".`;
|
|
29
139
|
}
|
|
30
|
-
|
|
140
|
+
const lineas = resultados.map((r, i) => `[${i + 1}] ${r.titulo}\n URL: ${r.url}\n ${r.resumen}`);
|
|
141
|
+
return `Resultados de búsqueda web para "${query}":\n\n${lineas.join("\n\n")}`;
|
|
31
142
|
}
|
|
32
143
|
catch (err) {
|
|
33
144
|
return `Error en búsqueda web: ${err?.message || String(err)}`;
|
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Escribe un archivo, entero o por partes.
|
|
3
|
+
*
|
|
4
|
+
* POR QUE EXISTE "anadir"
|
|
5
|
+
*
|
|
6
|
+
* La respuesta del modelo tiene un techo de salida. Cuando le piden una pagina
|
|
7
|
+
* entera, el contenido no cabe, la respuesta se corta a mitad de los argumentos
|
|
8
|
+
* y no se escribe nada. Se le decia "hazlo en varias llamadas mas pequenas"...
|
|
9
|
+
* y no habia forma de hacerlo: write_file solo sabia sobrescribir, asi que la
|
|
10
|
+
* segunda llamada borraba la primera. Le pediamos algo imposible y luego le
|
|
11
|
+
* echabamos la culpa.
|
|
12
|
+
*
|
|
13
|
+
* Con `modo: "anadir"` la instruccion pasa a ser ejecutable: primera llamada
|
|
14
|
+
* con el principio, las siguientes anadiendo, y el archivo sale entero.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeFile(filePath: string, content: string, cwd?: string, modo?: "sobrescribir" | "anadir"): Promise<string>;
|
package/dist/tools/writeFile.js
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
3
|
+
import { binaryExtensionLabel, fileLooksBinary } from "./binary.js";
|
|
4
|
+
/**
|
|
5
|
+
* Escribe un archivo, entero o por partes.
|
|
6
|
+
*
|
|
7
|
+
* POR QUE EXISTE "anadir"
|
|
8
|
+
*
|
|
9
|
+
* La respuesta del modelo tiene un techo de salida. Cuando le piden una pagina
|
|
10
|
+
* entera, el contenido no cabe, la respuesta se corta a mitad de los argumentos
|
|
11
|
+
* y no se escribe nada. Se le decia "hazlo en varias llamadas mas pequenas"...
|
|
12
|
+
* y no habia forma de hacerlo: write_file solo sabia sobrescribir, asi que la
|
|
13
|
+
* segunda llamada borraba la primera. Le pediamos algo imposible y luego le
|
|
14
|
+
* echabamos la culpa.
|
|
15
|
+
*
|
|
16
|
+
* Con `modo: "anadir"` la instruccion pasa a ser ejecutable: primera llamada
|
|
17
|
+
* con el principio, las siguientes anadiendo, y el archivo sale entero.
|
|
18
|
+
*/
|
|
19
|
+
export async function writeFile(filePath, content, cwd = process.cwd(), modo = "sobrescribir") {
|
|
4
20
|
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
5
21
|
const dir = path.dirname(resolved);
|
|
22
|
+
const isNew = !fs.existsSync(resolved);
|
|
23
|
+
// write_file es la ruta mas desprotegida del agente: no pasa por
|
|
24
|
+
// checkBeforeEdit (runner.ts no lo llama en este caso) y no pide confirmacion
|
|
25
|
+
// en ningun modo de permisos. Volcar un string UTF-8 sobre un png o un mp4 lo
|
|
26
|
+
// sustituye por basura, y ademas deja el snapshot de /undo apuntando a un
|
|
27
|
+
// archivo que ya no se puede reconstruir desde texto. Los bytes de un binario
|
|
28
|
+
// no salen de un string: si de verdad hay que reemplazarlo, se borra primero.
|
|
29
|
+
const etiqueta = binaryExtensionLabel(resolved);
|
|
30
|
+
if (etiqueta) {
|
|
31
|
+
return (`Error: "${filePath}" es un archivo binario (${etiqueta}). write_file escribe texto UTF-8, ` +
|
|
32
|
+
`asi que lo dejaria corrupto. Genera el archivo con la herramienta que corresponda ` +
|
|
33
|
+
`(create_image, run_command) o borralo antes si de verdad quieres reemplazarlo.`);
|
|
34
|
+
}
|
|
35
|
+
if (!isNew && fileLooksBinary(resolved)) {
|
|
36
|
+
return (`Error: "${filePath}" contiene bytes nulos: es binario, no texto. write_file lo dejaria corrupto. ` +
|
|
37
|
+
`Borralo antes si de verdad quieres reemplazarlo.`);
|
|
38
|
+
}
|
|
6
39
|
if (!fs.existsSync(dir)) {
|
|
7
40
|
fs.mkdirSync(dir, { recursive: true });
|
|
8
41
|
}
|
|
9
|
-
|
|
42
|
+
if (modo === "anadir") {
|
|
43
|
+
fs.appendFileSync(resolved, content, "utf-8");
|
|
44
|
+
const total = fs.statSync(resolved).size;
|
|
45
|
+
return (`✔ Anadido a "${filePath}" (${content.length} caracteres nuevos, ${total} en total). ` +
|
|
46
|
+
`Sigue con otra llamada en modo "anadir" si falta contenido.`);
|
|
47
|
+
}
|
|
10
48
|
fs.writeFileSync(resolved, content, "utf-8");
|
|
11
49
|
const action = isNew ? "creado" : "sobreescrito";
|
|
12
50
|
return `✔ Archivo "${filePath}" ${action} exitosamente (${content.length} caracteres).`;
|
package/dist/ui/interrupt.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Lo llama el prompt al abrirse, despues de coger el modo crudo. */
|
|
2
|
+
export declare function tomarTecladoParaPrompt(): void;
|
|
3
|
+
/** Lo llama el prompt al cerrarse, para que el siguiente turno pueda restaurar. */
|
|
4
|
+
export declare function devolverTecladoDelPrompt(): void;
|
|
2
5
|
export declare function releaseKeyboard(): void;
|
|
3
6
|
export declare class InterruptWatcher {
|
|
4
7
|
private controller;
|