chocolatito-code 1.6.13 → 1.6.15
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/COMPUTER-USE.md +37 -0
- package/README.md +29 -3
- package/dist/agent/historialDeCambios.d.ts +77 -0
- package/dist/agent/historialDeCambios.js +220 -0
- package/dist/agent/salidaDelAgente.d.ts +9 -0
- package/dist/agent/salidaDelAgente.js +19 -0
- package/dist/agent/undoManager.d.ts +21 -0
- package/dist/agent/undoManager.js +85 -5
- package/dist/config/permissions.js +4 -0
- package/dist/index.js +216 -27
- package/dist/prompts/systemPrompt.js +15 -11
- package/dist/servidor/archivos.d.ts +69 -0
- package/dist/servidor/archivos.js +156 -0
- package/dist/servidor/captura.d.ts +9 -0
- package/dist/servidor/captura.js +48 -0
- package/dist/servidor/fondo.d.ts +85 -0
- package/dist/servidor/fondo.js +134 -0
- package/dist/servidor/marca.d.ts +14 -0
- package/dist/servidor/marca.js +14 -0
- package/dist/servidor/pagina.d.ts +0 -26
- package/dist/servidor/pagina.js +337 -10
- package/dist/servidor/puente.d.ts +13 -0
- package/dist/servidor/puente.js +11 -0
- package/dist/servidor/servidor.d.ts +76 -2
- package/dist/servidor/servidor.js +245 -13
- package/dist/tools/computerUse.d.ts +11 -1
- package/dist/tools/computerUse.js +376 -34
- package/dist/tools/toolDefsComputer.js +39 -14
- package/dist/tools/visionBridge.d.ts +4 -2
- package/dist/tools/visionBridge.js +40 -15
- package/dist/tools/win/hostScript.js +300 -29
- package/dist/ui/comandos.js +1 -0
- package/dist/ui/ink/App.js +23 -3
- package/dist/ui/ink/Prompt.d.ts +5 -1
- package/dist/ui/ink/Prompt.js +92 -4
- package/dist/ui/ink/control.d.ts +2 -0
- package/dist/ui/ink/control.js +15 -0
- package/dist/ui/ink/montarApp.d.ts +0 -19
- package/dist/ui/ink/montarApp.js +3 -0
- package/dist/ui/permissionPrompt.js +44 -9
- package/dist/ui/pieFijo.js +5 -0
- package/dist/ui/prompt.js +6 -0
- package/dist/ui/renderer.d.ts +1 -0
- package/dist/ui/renderer.js +27 -2
- package/dist/ui/selector.js +5 -0
- package/dist/ui/spinner.d.ts +1 -0
- package/dist/ui/spinner.js +6 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,14 +17,15 @@ import { fijarMotor } from "./ui/motorActual.js";
|
|
|
17
17
|
import { elegirDeLista } from "./ui/selector.js";
|
|
18
18
|
import { entrarEnPantallaFija, pantallaFijaPedida, salirDePantallaFija } from "./ui/pantalla.js";
|
|
19
19
|
import { iniciarServidorWeb } from "./servidor/servidor.js";
|
|
20
|
-
import { iniciarCaptura } from "./servidor/captura.js";
|
|
20
|
+
import { anotarCrudoWeb, anotarWeb, iniciarCaptura } from "./servidor/captura.js";
|
|
21
|
+
import { copiarSalidaAlWeb } from "./agent/salidaDelAgente.js";
|
|
21
22
|
import { entregarEntrada, esperarEntradaWeb, marcarServirActivo } from "./servidor/puente.js";
|
|
22
23
|
import { montarArbol, desmontarArbol } from "./ui/ink/montarApp.js";
|
|
23
24
|
import { reafirmarModoCrudo } from "./ui/keyboardGuard.js";
|
|
24
25
|
import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
|
|
25
26
|
import { changeDir } from "./tools/changeDir.js";
|
|
26
27
|
import { gitDiff, gitCommit } from "./tools/gitTools.js";
|
|
27
|
-
import { renderUserPrompt } from "./ui/renderer.js";
|
|
28
|
+
import { renderUserPrompt, formatColorDiff } from "./ui/renderer.js";
|
|
28
29
|
import { parseFileMentions, buildPromptWithMentions, notaDeMencionesImplicitas } from "./agent/mentions.js";
|
|
29
30
|
import { SkillManager } from "./skills/manager.js";
|
|
30
31
|
import { MemoryManager } from "./memory/manager.js";
|
|
@@ -33,6 +34,8 @@ import { SessionManager } from "./sessions/manager.js";
|
|
|
33
34
|
import { prepararRestauracion, formatearOpcionesDeSesion } from "./sessions/resume.js";
|
|
34
35
|
import { auditGitDiff, formatAuditReport } from "./tools/gitAudit.js";
|
|
35
36
|
import { instalarKit, listarKits, quitarKit, crearKit } from "./kits/manager.js";
|
|
37
|
+
import { guardarSesion, lanzarEnFondo, olvidarSesion, pararSesion, sesionViva } from "./servidor/fondo.js";
|
|
38
|
+
import { nombreDeAnfitrion } from "./servidor/servidor.js";
|
|
36
39
|
import { leerCortes, rutaDelRegistro } from "./agent/cortes.js";
|
|
37
40
|
import { globalUndoManager } from "./agent/undoManager.js";
|
|
38
41
|
import { setAutoApprove } from "./ui/permissionPrompt.js";
|
|
@@ -220,8 +223,8 @@ async function main() {
|
|
|
220
223
|
// errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
|
|
221
224
|
const nodeViejo = comprobarNode();
|
|
222
225
|
if (nodeViejo) {
|
|
223
|
-
console.error(chalk.red(`
|
|
224
|
-
${nodeViejo}
|
|
226
|
+
console.error(chalk.red(`
|
|
227
|
+
${nodeViejo}
|
|
225
228
|
`));
|
|
226
229
|
process.exit(EXIT_USO);
|
|
227
230
|
}
|
|
@@ -235,13 +238,58 @@ ${nodeViejo}
|
|
|
235
238
|
// red local, que es lo que hace falta para entrar desde el movil.
|
|
236
239
|
const enRed = rawArgs.includes("--servir-red");
|
|
237
240
|
const quiereServir = enRed || rawArgs.includes("--servir");
|
|
241
|
+
// --fondo suelta la sesion del terminal; --parar y --estado la gobiernan
|
|
242
|
+
// desde fuera, sin tener que buscar la ventana que la lanzo.
|
|
243
|
+
const quiereFondo = rawArgs.includes("--fondo");
|
|
244
|
+
const quiereParar = rawArgs.includes("--parar");
|
|
245
|
+
const quiereEstado = rawArgs.includes("--estado");
|
|
246
|
+
// --tunel autoriza UN nombre de dominio mas, para entrar por cloudflared o
|
|
247
|
+
// ngrok desde fuera de casa. Uno concreto: la defensa contra el DNS rebinding
|
|
248
|
+
// sigue en pie para todos los demas.
|
|
249
|
+
const posTunel = rawArgs.indexOf("--tunel");
|
|
250
|
+
const tunelWeb = posTunel >= 0 ? String(rawArgs[posTunel + 1] || "") : "";
|
|
238
251
|
const posPuerto = rawArgs.indexOf("--puerto");
|
|
239
252
|
const puertoWeb = posPuerto >= 0 ? Number(rawArgs[posPuerto + 1]) || 0 : 0;
|
|
240
253
|
// Ojo: lo que sobra de argv se convierte en la orden a ejecutar. Una bandera
|
|
241
254
|
// que no se filtre aqui no es una bandera ignorada, es una orden al modelo.
|
|
242
|
-
const banderas = new Set([
|
|
243
|
-
|
|
255
|
+
const banderas = new Set([
|
|
256
|
+
"--yes",
|
|
257
|
+
"-y",
|
|
258
|
+
"--servir",
|
|
259
|
+
"--servir-red",
|
|
260
|
+
"--puerto",
|
|
261
|
+
"--fondo",
|
|
262
|
+
"--parar",
|
|
263
|
+
"--estado",
|
|
264
|
+
"--tunel",
|
|
265
|
+
]);
|
|
266
|
+
const args = rawArgs.filter((a, i) => !banderas.has(a) &&
|
|
267
|
+
!(posPuerto >= 0 && i === posPuerto + 1 && puertoWeb > 0) &&
|
|
268
|
+
!(posTunel >= 0 && i === posTunel + 1 && !!tunelWeb));
|
|
244
269
|
const singlePrompt = args.join(" ").trim();
|
|
270
|
+
// ESTAS DOS NO ARRANCAN NADA, y por eso van aqui arriba: montar el agente
|
|
271
|
+
// entero -API key, skills, MCP- para luego solo decir un PID seria hacer
|
|
272
|
+
// esperar al usuario por nada, y ademas pediria la clave a quien solo quiere
|
|
273
|
+
// apagar la sesion de ayer.
|
|
274
|
+
if (quiereParar) {
|
|
275
|
+
const res = await pararSesion();
|
|
276
|
+
console.log(res.ok ? chalk.green(`\n ✔ ${res.mensaje}\n`) : chalk.yellow(`\n ${res.mensaje}\n`));
|
|
277
|
+
process.exit(res.ok ? 0 : 1);
|
|
278
|
+
}
|
|
279
|
+
if (quiereEstado) {
|
|
280
|
+
const viva = sesionViva();
|
|
281
|
+
if (!viva) {
|
|
282
|
+
console.log(chalk.gray("\n No hay ninguna sesion de fondo abierta.\n"));
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
console.log(chalk.bold.hex("#e8833a")("\n 🦊 Sesion de fondo abierta"));
|
|
286
|
+
console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
|
|
287
|
+
console.log(` ${chalk.bold("Carpeta:")} ${chalk.white(viva.carpeta)}`);
|
|
288
|
+
console.log(` ${chalk.bold("Desde:")} ${chalk.white(new Date(viva.desde).toLocaleString())}`);
|
|
289
|
+
console.log(chalk.gray(` pid ${viva.pid} · ${viva.enRed ? "visible en tu red" : "solo esta maquina"}`));
|
|
290
|
+
console.log(chalk.gray("\n Para cerrarla: chocolatito --parar\n"));
|
|
291
|
+
process.exit(0);
|
|
292
|
+
}
|
|
245
293
|
// Los errores de uso se resuelven ANTES de la licencia y de la API key: no
|
|
246
294
|
// dependen de estar suscrito, y en CI hay que poder distinguir "lo has
|
|
247
295
|
// invocado mal" de "el agente no pudo".
|
|
@@ -283,6 +331,9 @@ ${nodeViejo}
|
|
|
283
331
|
let isPlanMode = false;
|
|
284
332
|
let systemPrompt = buildSystemPrompt(context, currentModel, skillManager, memoryManager, isPlanMode);
|
|
285
333
|
const agent = new AgentLoop(apiKey, getEngineBaseUrl(), currentModel, systemPrompt);
|
|
334
|
+
// El historial de cambios es de CADA proyecto y vive en su carpeta, asi que
|
|
335
|
+
// hay que decirle cual es. Con /cd se vuelve a fijar mas abajo.
|
|
336
|
+
globalUndoManager.fijarCarpeta(agent.currentCwd);
|
|
286
337
|
// Shift+Tab (en el prompt, durante la generacion o dentro del propio dialogo)
|
|
287
338
|
// cambia el modo de permisos del agente al vuelo. El modo plan no entra en el
|
|
288
339
|
// ciclo: se sigue activando con /plan, para no dejarlo a un golpe de tecla.
|
|
@@ -408,16 +459,87 @@ ${nodeViejo}
|
|
|
408
459
|
// reemplazo los cuatro canales de console para mandarlos a la transcripcion, y
|
|
409
460
|
// la captura tiene que envolver ESA version. Al reves, pantalla envolveria a la
|
|
410
461
|
// captura y al navegador no llegaria nada. Ver servidor/captura.ts.
|
|
462
|
+
// SOLTARSE DEL TERMINAL
|
|
463
|
+
//
|
|
464
|
+
// Se relanza el mismo programa con los mismos argumentos, sin --fondo, y este
|
|
465
|
+
// proceso se va. El hijo no hereda ni la entrada ni la salida: la ventana se
|
|
466
|
+
// puede cerrar y la sesion sigue.
|
|
467
|
+
//
|
|
468
|
+
// El enlace lo imprime el PADRE, pero el token lo genera el HIJO. Asi que en
|
|
469
|
+
// vez de inventar un canal para pasarlo, se espera a que el hijo escriba su
|
|
470
|
+
// archivo de sesion. Eso ademas confirma que arranco de verdad: si el hijo
|
|
471
|
+
// revienta -puerto ocupado, licencia caducada-, el archivo no aparece y se
|
|
472
|
+
// dice que fallo, en lugar de imprimir un enlace a nada.
|
|
473
|
+
if (quiereServir && quiereFondo && !process.env["CHOCOLATITO_EN_FONDO"]) {
|
|
474
|
+
const yaAbierta = sesionViva();
|
|
475
|
+
if (yaAbierta) {
|
|
476
|
+
console.log(chalk.yellow("\n Ya hay una sesion de fondo abierta."));
|
|
477
|
+
console.log(` ${chalk.cyan(yaAbierta.url)}\n`);
|
|
478
|
+
console.log(chalk.gray(" Para cerrarla: chocolatito --parar\n"));
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
olvidarSesion();
|
|
482
|
+
const pid = lanzarEnFondo(rawArgs.filter((a) => a !== "--fondo"));
|
|
483
|
+
let viva = null;
|
|
484
|
+
for (let intento = 0; intento < 100 && !viva; intento++) {
|
|
485
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
486
|
+
viva = sesionViva();
|
|
487
|
+
}
|
|
488
|
+
if (!viva) {
|
|
489
|
+
console.log(chalk.red("\n La sesion de fondo no llego a arrancar."));
|
|
490
|
+
console.log(chalk.gray(` Prueba sin --fondo para ver el error (pid ${pid}).\n`));
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
console.log(chalk.hex("#e8833a")("\n 🦊 Sesion abierta en segundo plano\n"));
|
|
494
|
+
console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
|
|
495
|
+
console.log(chalk.gray("\n Ya puedes cerrar esta ventana: la sesion sigue."));
|
|
496
|
+
console.log(chalk.gray(" chocolatito --estado · chocolatito --parar\n"));
|
|
497
|
+
process.exit(0);
|
|
498
|
+
}
|
|
411
499
|
if (quiereServir) {
|
|
412
500
|
try {
|
|
413
|
-
|
|
414
|
-
|
|
501
|
+
const web = await iniciarServidorWeb({
|
|
502
|
+
puerto: puertoWeb || undefined,
|
|
503
|
+
enRed,
|
|
504
|
+
anfitrionAutorizado: tunelWeb || undefined,
|
|
505
|
+
// La carpeta se pregunta en cada peticion, no se copia aqui: con /cd el
|
|
506
|
+
// agente se cambia a mitad de sesion y lo que suba el movil tiene que
|
|
507
|
+
// caer donde se esta trabajando AHORA.
|
|
508
|
+
carpeta: () => agent.currentCwd,
|
|
509
|
+
});
|
|
415
510
|
marcarServirActivo(true);
|
|
511
|
+
// Solo el que corre suelto deja su ficha: una sesion normal muere con su
|
|
512
|
+
// terminal y anotarla solo serviria para que --estado mintiera despues.
|
|
513
|
+
if (process.env["CHOCOLATITO_EN_FONDO"]) {
|
|
514
|
+
guardarSesion({
|
|
515
|
+
pid: process.pid,
|
|
516
|
+
puerto: web.puerto,
|
|
517
|
+
token: web.token,
|
|
518
|
+
url: web.urlsDeRed[0] || web.url,
|
|
519
|
+
carpeta: agent.currentCwd,
|
|
520
|
+
desde: new Date().toISOString(),
|
|
521
|
+
enRed,
|
|
522
|
+
});
|
|
523
|
+
const limpiar = () => olvidarSesion();
|
|
524
|
+
process.once("exit", limpiar);
|
|
525
|
+
process.once("SIGINT", () => {
|
|
526
|
+
limpiar();
|
|
527
|
+
process.exit(0);
|
|
528
|
+
});
|
|
529
|
+
process.once("SIGTERM", () => {
|
|
530
|
+
limpiar();
|
|
531
|
+
process.exit(0);
|
|
532
|
+
});
|
|
533
|
+
}
|
|
416
534
|
console.log(chalk.hex("#e8833a")("\n 🦊 Sesión abierta en el navegador\n"));
|
|
417
535
|
console.log(` ${chalk.bold("Aquí:")} ${chalk.cyan(web.url)}`);
|
|
418
536
|
for (const dir of web.urlsDeRed) {
|
|
419
537
|
console.log(` ${chalk.bold("Del móvil:")} ${chalk.cyan(dir)}`);
|
|
420
538
|
}
|
|
539
|
+
if (tunelWeb) {
|
|
540
|
+
console.log(chalk.yellow(`\n ⚠ Autorizado el tunel ${nombreDeAnfitrion(tunelWeb)}`) +
|
|
541
|
+
chalk.gray("\n Ese enlace sale a internet: quien lo tenga entero entra. Cierrala al acabar."));
|
|
542
|
+
}
|
|
421
543
|
if (!enRed) {
|
|
422
544
|
console.log(chalk.gray("\n Solo desde esta máquina. Para entrar desde el móvil: --servir-red"));
|
|
423
545
|
}
|
|
@@ -427,6 +549,28 @@ ${nodeViejo}
|
|
|
427
549
|
"\n puede usar esta sesión. No lo pegues en ningún sitio compartido."));
|
|
428
550
|
}
|
|
429
551
|
console.log(chalk.gray("\n Lo que escribas aquí ya no se lee: manda el navegador y este terminal es el monitor.\n"));
|
|
552
|
+
// LA CAPTURA EMPIEZA AQUI, Y NO ANTES, POR DOS MOTIVOS
|
|
553
|
+
//
|
|
554
|
+
// 1. EL TOKEN. Lo que se acaba de imprimir incluye "Aqui: http://...?t=..."
|
|
555
|
+
// con el token entero. Eso es para el que esta delante del PC; copiarlo
|
|
556
|
+
// a la pagina lo deja escrito en la pantalla del movil, a la vista de
|
|
557
|
+
// quien pase por al lado y en cualquier captura que se haga.
|
|
558
|
+
// 2. NO SIRVE DE NADA ALLI. "Para entrar desde el movil: --servir-red" se
|
|
559
|
+
// lo estas leyendo YA desde el movil. Son instrucciones para llegar a
|
|
560
|
+
// un sitio en el que ya estas.
|
|
561
|
+
//
|
|
562
|
+
// Asi que al navegador se le da una bienvenida suya, corta: el zorro y la
|
|
563
|
+
// carpeta, que es lo unico que hace falta saber al abrir.
|
|
564
|
+
iniciarCaptura();
|
|
565
|
+
// Y lo que contesta el agente, que NO va por console: sale por el pie fijo
|
|
566
|
+
// a process.stdout, asi que hay que copiarlo aparte o el movil se queda mudo.
|
|
567
|
+
copiarSalidaAlWeb((texto) => anotarCrudoWeb(texto));
|
|
568
|
+
// La cabecera de la pagina ya lleva el zorro oficial y el nombre, asi que
|
|
569
|
+
// repetirlo aqui con un emoji era decir dos veces lo mismo, y con dos
|
|
570
|
+
// dibujos distintos del mismo zorro. Queda la carpeta, que es lo unico que
|
|
571
|
+
// hace falta saber al abrir: en que proyecto estas.
|
|
572
|
+
anotarWeb(chalk.gray(` ${agent.currentCwd}`));
|
|
573
|
+
anotarWeb("");
|
|
430
574
|
// Una orden en la linea de comandos con --servir es la PRIMERA del turno,
|
|
431
575
|
// no una tarea suelta: entra por la misma puerta que lo que llegue del movil.
|
|
432
576
|
if (singlePrompt)
|
|
@@ -504,6 +648,48 @@ ${nodeViejo}
|
|
|
504
648
|
}
|
|
505
649
|
continue;
|
|
506
650
|
}
|
|
651
|
+
if (trimmed === "/rewind" || trimmed.startsWith("/rewind ")) {
|
|
652
|
+
const pedido = trimmed.slice(7).trim();
|
|
653
|
+
const turnos = globalUndoManager.turnos();
|
|
654
|
+
if (turnos.length === 0) {
|
|
655
|
+
console.log(chalk.gray("\n No hay nada guardado que deshacer en este proyecto.\n"));
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (!pedido) {
|
|
659
|
+
console.log(chalk.bold.hex("#D97757")("\n⏪ VOLVER ATRÁS"));
|
|
660
|
+
console.log(chalk.gray(" Lo que Chocolatito ha tocado en esta carpeta, de lo más nuevo a lo más viejo:\n"));
|
|
661
|
+
// Del mas nuevo al mas viejo: lo que quieres deshacer casi siempre es lo
|
|
662
|
+
// ultimo, y leerlo del reves obliga a contar renglones.
|
|
663
|
+
const alReves = [...turnos].reverse();
|
|
664
|
+
alReves.forEach((t, i) => {
|
|
665
|
+
const cuando = new Date(t.cuando).toLocaleString();
|
|
666
|
+
const conCopia = t.archivos.filter((a) => a.tipo === "file").length;
|
|
667
|
+
const creados = t.archivos.filter((a) => a.tipo === "absent").length;
|
|
668
|
+
const sinCopia = t.archivos.filter((a) => a.tipo === "unrestorable").length;
|
|
669
|
+
const piezas = [
|
|
670
|
+
conCopia ? `${conCopia} restaurable(s)` : "",
|
|
671
|
+
creados ? `${creados} creado(s)` : "",
|
|
672
|
+
sinCopia ? chalk.yellow(`${sinCopia} SIN copia`) : "",
|
|
673
|
+
].filter(Boolean);
|
|
674
|
+
console.log(` ${chalk.bold.white(String(i + 1).padStart(2))}. ${chalk.white(t.descripcion.slice(0, 60))}`);
|
|
675
|
+
console.log(` ${chalk.gray(cuando)} ${chalk.gray(piezas.join(" · "))}`);
|
|
676
|
+
});
|
|
677
|
+
console.log(chalk.gray("\n /rewind <n> vuelve a como estaba ANTES de ese turno (y deshace los de después).\n"));
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const indice = Number(pedido);
|
|
681
|
+
if (!Number.isInteger(indice) || indice < 1 || indice > turnos.length) {
|
|
682
|
+
console.log(chalk.yellow(`\n Elige un número del 1 al ${turnos.length}. /rewind a secas los lista.\n`));
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const elegido = [...turnos].reverse()[indice - 1];
|
|
686
|
+
const res = globalUndoManager.volverAntesDe(elegido.id);
|
|
687
|
+
console.log(res.success ? chalk.green(`\n${res.message}`) : chalk.yellow(`\n${res.message}`));
|
|
688
|
+
res.restoredFiles.forEach((r) => console.log(` ${chalk.gray("•")} ${chalk.white(r)}`));
|
|
689
|
+
res.failures.forEach((r) => console.log(` ${chalk.red("✗")} ${chalk.yellow(r)}`));
|
|
690
|
+
console.log("");
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
507
693
|
if (trimmed === "/undo") {
|
|
508
694
|
const undoRes = globalUndoManager.undoLast();
|
|
509
695
|
// Lo que no se pudo deshacer se imprime siempre, tambien cuando algo si se
|
|
@@ -522,15 +708,15 @@ ${nodeViejo}
|
|
|
522
708
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
523
709
|
.map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
|
|
524
710
|
.join("\n---\n\n");
|
|
525
|
-
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
526
|
-
**Fecha:** ${new Date().toLocaleString()}
|
|
527
|
-
**Directorio:** \`${agent.currentCwd}\`
|
|
528
|
-
**Motor:** ${currentModel.name}
|
|
529
|
-
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
530
|
-
|
|
531
|
-
---
|
|
532
|
-
|
|
533
|
-
${sessionData}
|
|
711
|
+
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
712
|
+
**Fecha:** ${new Date().toLocaleString()}
|
|
713
|
+
**Directorio:** \`${agent.currentCwd}\`
|
|
714
|
+
**Motor:** ${currentModel.name}
|
|
715
|
+
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
${sessionData}
|
|
534
720
|
`;
|
|
535
721
|
fs.writeFileSync(exportPath, mdContent, "utf-8");
|
|
536
722
|
console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
|
|
@@ -746,12 +932,12 @@ ${sessionData}
|
|
|
746
932
|
}
|
|
747
933
|
console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
|
|
748
934
|
console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
|
|
749
|
-
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
750
|
-
Tu objetivo principal es: "${goalText}"
|
|
751
|
-
Directivas de ejecución:
|
|
752
|
-
1. Formula un plan de ejecución mental paso a paso.
|
|
753
|
-
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
754
|
-
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
935
|
+
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
936
|
+
Tu objetivo principal es: "${goalText}"
|
|
937
|
+
Directivas de ejecución:
|
|
938
|
+
1. Formula un plan de ejecución mental paso a paso.
|
|
939
|
+
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
940
|
+
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
755
941
|
4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
|
|
756
942
|
renderUserPrompt(`/goal ${goalText}`);
|
|
757
943
|
await agent.runTask(autonomousPrompt);
|
|
@@ -774,7 +960,7 @@ Directivas de ejecución:
|
|
|
774
960
|
if (trimmed === "/diff") {
|
|
775
961
|
const diffOut = await gitDiff(false, agent.currentCwd);
|
|
776
962
|
console.log(chalk.bold.hex("#D97757")("\n📝 DIFERENCIAS DE GIT (DIFF):"));
|
|
777
|
-
console.log(diffOut ?
|
|
963
|
+
console.log(diffOut ? `${formatColorDiff(diffOut)}\n` : chalk.green("✔ Sin cambios pendientes.") + "\n");
|
|
778
964
|
continue;
|
|
779
965
|
}
|
|
780
966
|
if (trimmed === "/audit" || trimmed === "/review" || trimmed.startsWith("/audit ") || trimmed.startsWith("/review ")) {
|
|
@@ -799,6 +985,9 @@ Directivas de ejecución:
|
|
|
799
985
|
if (res.success) {
|
|
800
986
|
agent.currentCwd = res.newCwd;
|
|
801
987
|
memoryManager.setCwd(agent.currentCwd);
|
|
988
|
+
// El historial de cambios es de cada proyecto: al cambiar de carpeta se
|
|
989
|
+
// cambia de historial, o /rewind ensenaria los turnos de otro sitio.
|
|
990
|
+
globalUndoManager.fijarCarpeta(agent.currentCwd);
|
|
802
991
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
803
992
|
systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
804
993
|
agent.clearHistory(systemPrompt);
|
|
@@ -861,12 +1050,12 @@ Directivas de ejecución:
|
|
|
861
1050
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
862
1051
|
const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
863
1052
|
agent.setEspectroModel(currentModel, updatedPrompt);
|
|
864
|
-
console.log(chalk.green(`
|
|
1053
|
+
console.log(chalk.green(`
|
|
865
1054
|
✔ Nivel actualizado a `) +
|
|
866
1055
|
chalk.bold.hex("#A855F7")(currentModel.name) +
|
|
867
1056
|
chalk.gray(` (${currentModel.effortLevel})`) +
|
|
868
|
-
`
|
|
869
|
-
${chalk.italic.gray(currentModel.description)}
|
|
1057
|
+
`
|
|
1058
|
+
${chalk.italic.gray(currentModel.description)}
|
|
870
1059
|
`);
|
|
871
1060
|
// EL TECLADO SE QUEDABA TRABADO AL CAMBIAR DE MOTOR
|
|
872
1061
|
//
|
|
@@ -172,17 +172,21 @@ Puedes delegar tareas aisladas con 'spawn_agent':
|
|
|
172
172
|
NUNCA pidas al usuario su contraseña ni la escribas. Si hace falta una cuenta, es
|
|
173
173
|
él quien inicia sesión a mano.
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
175
|
+
APLICACIONES DE ESCRITORIO: usa 'computer_use' (nivel Astra / OpenAI Operator):
|
|
176
|
+
* Flujo preferente: computer_use(list_windows) → computer_use(ui_snapshot, window:"...")
|
|
177
|
+
→ computer_use(ui_click, ref:N).
|
|
178
|
+
* Pasa SIEMPRE 'window'. Sin ella actúas sobre lo que esté delante, que es
|
|
179
|
+
exactamente como se acaba escribiendo en la ventana equivocada.
|
|
180
|
+
* El modo por defecto es 'background': no robes el foco salvo que sea imprescindible.
|
|
181
|
+
* FLUJOS RÁPIDOS (ACTION CHAINING): Para secuencias interactivas comunes
|
|
182
|
+
(ej. clic en campo -> escribir texto -> pulsar Enter -> esperar resultado),
|
|
183
|
+
usa action: 'sequence' con 'steps'. Ejecuta todo en milisegundos en un solo turno sin latencia.
|
|
184
|
+
* CUANDO NO HAY ÁRBOL DE ACCESIBILIDAD (lienzos, juegos, vídeo, Flutter, interfaces gráficas complejas):
|
|
185
|
+
- Usa 'screenshot' con 'grid: true' para activar la cuadrícula de coordenadas milimétricas y situar controles con exactitud.
|
|
186
|
+
- Usa 'screenshot' con 'region: [x1,y1,x2,y2]' para inspeccionar zonas pequeñas o detalles finos en resolución nativa 1:1.
|
|
187
|
+
- Usa 'wait_visual_change' para esperar animaciones, cargas o cambios visuales cuando la app no emite eventos de accesibilidad.
|
|
188
|
+
- Usa 'find_element' para localizar un control por texto en el árbol o visualmente mediante visión multimodal.
|
|
189
|
+
* COORDENADAS: Soporta píxeles físicos o coordenadas normalizadas (0..1000 con normalized: true). Si 'ui_click' no tiene patrón nativo, se auto-recupera clicando el centro del elemento.
|
|
186
190
|
|
|
187
191
|
PROHIBIDO: 'taskkill' o matar procesos de Chrome del usuario.
|
|
188
192
|
8. LO QUE PRODUCES ES DEL USUARIO, NO TUYO:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARCHIVOS QUE ENTRAN Y ARCHIVOS QUE SALEN
|
|
3
|
+
*
|
|
4
|
+
* DE DONDE SALE
|
|
5
|
+
*
|
|
6
|
+
* La pagina de `--servir` solo sabia texto. Ni un `<input type=file>`, ni una
|
|
7
|
+
* sola `<img>`. Y eso deja el ciclo de trabajo cojo por los dos extremos:
|
|
8
|
+
*
|
|
9
|
+
* - Lo que tienes en el movil -la foto, el video que te mando el cliente, la
|
|
10
|
+
* captura del error- no hay forma de metarlo. Habia que pasarlo antes al PC.
|
|
11
|
+
* - Lo que el agente produce se contesta con la RUTA. En un movil, "lo tienes
|
|
12
|
+
* en C:\Users\...\salida\pieza-01.png" no sirve para nada: no puedes mirarlo,
|
|
13
|
+
* asi que no puedes opinar, asi que no puedes trabajar.
|
|
14
|
+
*
|
|
15
|
+
* LAS DOS REGLAS
|
|
16
|
+
*
|
|
17
|
+
* 1. LO QUE ENTRA CAE EN UN SOLO SITIO: `entradas/` de la carpeta de trabajo.
|
|
18
|
+
* No se deja elegir destino. Un nombre de archivo que llega por la red no
|
|
19
|
+
* es un nombre: es lo que alguien escribio al otro lado, y "..\..\.ssh\id_rsa"
|
|
20
|
+
* tambien es un nombre. Se le quita todo lo que parezca una ruta y se queda
|
|
21
|
+
* el nombre pelado.
|
|
22
|
+
*
|
|
23
|
+
* 2. LO QUE SALE NO PUEDE SALIRSE DE LA CARPETA DE TRABAJO. Se resuelve la
|
|
24
|
+
* ruta de verdad -con enlaces simbolicos ya seguidos- y se comprueba que
|
|
25
|
+
* sigue estando dentro. Sin eso, `?ruta=../../.chocolatitorc` serviria la
|
|
26
|
+
* clave de API por HTTP a quien tenga el enlace.
|
|
27
|
+
*
|
|
28
|
+
* Las dos comprobaciones viven aqui y no en el servidor a proposito: son la
|
|
29
|
+
* clase de cosa que se copia mal cuando se reparte entre tres sitios.
|
|
30
|
+
*/
|
|
31
|
+
/** Lo mas grande que se acepta de una vez. Un video de movil cabe de sobra. */
|
|
32
|
+
export declare const LIMITE_DE_SUBIDA: number;
|
|
33
|
+
/** Donde caen los archivos que llegan del navegador. */
|
|
34
|
+
export declare const CARPETA_DE_ENTRADAS = "entradas";
|
|
35
|
+
export declare function tipoDelArchivo(ruta: string): string;
|
|
36
|
+
export declare function esImagen(ruta: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Deja un nombre de archivo en algo que no pueda salirse de su carpeta.
|
|
39
|
+
*
|
|
40
|
+
* No vale con quitar "..": en Windows tambien separa la barra invertida, y
|
|
41
|
+
* "C:algo" es una ruta relativa al disco C. Se parte por los dos separadores y
|
|
42
|
+
* se toma el ultimo trozo, que es lo unico que de verdad es un nombre.
|
|
43
|
+
*/
|
|
44
|
+
export declare function nombreSeguro(propuesto: string): string;
|
|
45
|
+
/** Si ya existe, se numera en vez de pisar lo que hubiera. */
|
|
46
|
+
export declare function rutaLibre(carpeta: string, nombre: string): string;
|
|
47
|
+
export interface Guardado {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
ruta?: string;
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function guardarSubida(cwd: string, nombrePropuesto: string, datos: Buffer): Guardado;
|
|
53
|
+
/**
|
|
54
|
+
* Comprueba que una ruta pedida por el navegador se puede servir.
|
|
55
|
+
*
|
|
56
|
+
* Devuelve la ruta real o null. Null es un 404 siempre, tambien cuando el
|
|
57
|
+
* archivo existe pero esta fuera: decir "existe pero no te lo doy" ya es contar
|
|
58
|
+
* algo del disco de alguien a quien no le hemos preguntado.
|
|
59
|
+
*/
|
|
60
|
+
export declare function rutaServible(cwd: string, pedida: string): string | null;
|
|
61
|
+
/**
|
|
62
|
+
* La linea que se le mete a la conversacion cuando llegan archivos.
|
|
63
|
+
*
|
|
64
|
+
* Lleva las rutas COMPLETAS y no los nombres: el modelo tiene que poder
|
|
65
|
+
* abrirlos sin adivinar donde cayeron, y "la foto que acabo de subir" no es una
|
|
66
|
+
* ruta. Y se dice que vienen del navegador, porque cambia lo que hay que hacer
|
|
67
|
+
* con ellos: no son archivos del proyecto, son material que acaba de llegar.
|
|
68
|
+
*/
|
|
69
|
+
export declare function avisoDeSubida(rutas: string[]): string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* ARCHIVOS QUE ENTRAN Y ARCHIVOS QUE SALEN
|
|
5
|
+
*
|
|
6
|
+
* DE DONDE SALE
|
|
7
|
+
*
|
|
8
|
+
* La pagina de `--servir` solo sabia texto. Ni un `<input type=file>`, ni una
|
|
9
|
+
* sola `<img>`. Y eso deja el ciclo de trabajo cojo por los dos extremos:
|
|
10
|
+
*
|
|
11
|
+
* - Lo que tienes en el movil -la foto, el video que te mando el cliente, la
|
|
12
|
+
* captura del error- no hay forma de metarlo. Habia que pasarlo antes al PC.
|
|
13
|
+
* - Lo que el agente produce se contesta con la RUTA. En un movil, "lo tienes
|
|
14
|
+
* en C:\Users\...\salida\pieza-01.png" no sirve para nada: no puedes mirarlo,
|
|
15
|
+
* asi que no puedes opinar, asi que no puedes trabajar.
|
|
16
|
+
*
|
|
17
|
+
* LAS DOS REGLAS
|
|
18
|
+
*
|
|
19
|
+
* 1. LO QUE ENTRA CAE EN UN SOLO SITIO: `entradas/` de la carpeta de trabajo.
|
|
20
|
+
* No se deja elegir destino. Un nombre de archivo que llega por la red no
|
|
21
|
+
* es un nombre: es lo que alguien escribio al otro lado, y "..\..\.ssh\id_rsa"
|
|
22
|
+
* tambien es un nombre. Se le quita todo lo que parezca una ruta y se queda
|
|
23
|
+
* el nombre pelado.
|
|
24
|
+
*
|
|
25
|
+
* 2. LO QUE SALE NO PUEDE SALIRSE DE LA CARPETA DE TRABAJO. Se resuelve la
|
|
26
|
+
* ruta de verdad -con enlaces simbolicos ya seguidos- y se comprueba que
|
|
27
|
+
* sigue estando dentro. Sin eso, `?ruta=../../.chocolatitorc` serviria la
|
|
28
|
+
* clave de API por HTTP a quien tenga el enlace.
|
|
29
|
+
*
|
|
30
|
+
* Las dos comprobaciones viven aqui y no en el servidor a proposito: son la
|
|
31
|
+
* clase de cosa que se copia mal cuando se reparte entre tres sitios.
|
|
32
|
+
*/
|
|
33
|
+
/** Lo mas grande que se acepta de una vez. Un video de movil cabe de sobra. */
|
|
34
|
+
export const LIMITE_DE_SUBIDA = 64 * 1024 * 1024;
|
|
35
|
+
/** Donde caen los archivos que llegan del navegador. */
|
|
36
|
+
export const CARPETA_DE_ENTRADAS = "entradas";
|
|
37
|
+
const TIPOS = {
|
|
38
|
+
".png": "image/png",
|
|
39
|
+
".jpg": "image/jpeg",
|
|
40
|
+
".jpeg": "image/jpeg",
|
|
41
|
+
".webp": "image/webp",
|
|
42
|
+
".gif": "image/gif",
|
|
43
|
+
".svg": "image/svg+xml",
|
|
44
|
+
".bmp": "image/bmp",
|
|
45
|
+
".avif": "image/avif",
|
|
46
|
+
".pdf": "application/pdf",
|
|
47
|
+
".mp4": "video/mp4",
|
|
48
|
+
".webm": "video/webm",
|
|
49
|
+
".mov": "video/quicktime",
|
|
50
|
+
".mp3": "audio/mpeg",
|
|
51
|
+
".wav": "audio/wav",
|
|
52
|
+
".ogg": "audio/ogg",
|
|
53
|
+
".txt": "text/plain; charset=utf-8",
|
|
54
|
+
".md": "text/plain; charset=utf-8",
|
|
55
|
+
".json": "application/json",
|
|
56
|
+
".csv": "text/csv; charset=utf-8",
|
|
57
|
+
};
|
|
58
|
+
export function tipoDelArchivo(ruta) {
|
|
59
|
+
return TIPOS[path.extname(ruta).toLowerCase()] || "application/octet-stream";
|
|
60
|
+
}
|
|
61
|
+
export function esImagen(ruta) {
|
|
62
|
+
return tipoDelArchivo(ruta).startsWith("image/");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deja un nombre de archivo en algo que no pueda salirse de su carpeta.
|
|
66
|
+
*
|
|
67
|
+
* No vale con quitar "..": en Windows tambien separa la barra invertida, y
|
|
68
|
+
* "C:algo" es una ruta relativa al disco C. Se parte por los dos separadores y
|
|
69
|
+
* se toma el ultimo trozo, que es lo unico que de verdad es un nombre.
|
|
70
|
+
*/
|
|
71
|
+
export function nombreSeguro(propuesto) {
|
|
72
|
+
const ultimo = String(propuesto || "")
|
|
73
|
+
.split(/[\\/]/)
|
|
74
|
+
.pop()
|
|
75
|
+
.replace(/^\.+/, "") // nada de ".." ni de archivos ocultos por accidente
|
|
76
|
+
.replace(/[\x00-\x1f<>:"|?*]/g, "") // lo que Windows no admite, y los de control
|
|
77
|
+
.trim();
|
|
78
|
+
const limpio = ultimo.slice(0, 120);
|
|
79
|
+
return limpio || `archivo-${Date.now()}`;
|
|
80
|
+
}
|
|
81
|
+
/** Si ya existe, se numera en vez de pisar lo que hubiera. */
|
|
82
|
+
export function rutaLibre(carpeta, nombre) {
|
|
83
|
+
const ext = path.extname(nombre);
|
|
84
|
+
const base = path.basename(nombre, ext);
|
|
85
|
+
let intento = path.join(carpeta, nombre);
|
|
86
|
+
let n = 2;
|
|
87
|
+
while (fs.existsSync(intento)) {
|
|
88
|
+
intento = path.join(carpeta, `${base}-${n}${ext}`);
|
|
89
|
+
n++;
|
|
90
|
+
if (n > 9999)
|
|
91
|
+
return path.join(carpeta, `${base}-${Date.now()}${ext}`);
|
|
92
|
+
}
|
|
93
|
+
return intento;
|
|
94
|
+
}
|
|
95
|
+
export function guardarSubida(cwd, nombrePropuesto, datos) {
|
|
96
|
+
if (datos.length === 0)
|
|
97
|
+
return { ok: false, error: "El archivo llego vacio." };
|
|
98
|
+
if (datos.length > LIMITE_DE_SUBIDA) {
|
|
99
|
+
return { ok: false, error: `El archivo pasa de ${Math.round(LIMITE_DE_SUBIDA / 1024 / 1024)} MB.` };
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const carpeta = path.join(cwd, CARPETA_DE_ENTRADAS);
|
|
103
|
+
fs.mkdirSync(carpeta, { recursive: true });
|
|
104
|
+
const destino = rutaLibre(carpeta, nombreSeguro(nombrePropuesto));
|
|
105
|
+
fs.writeFileSync(destino, datos);
|
|
106
|
+
return { ok: true, ruta: destino };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return { ok: false, error: err?.message || String(err) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Comprueba que una ruta pedida por el navegador se puede servir.
|
|
114
|
+
*
|
|
115
|
+
* Devuelve la ruta real o null. Null es un 404 siempre, tambien cuando el
|
|
116
|
+
* archivo existe pero esta fuera: decir "existe pero no te lo doy" ya es contar
|
|
117
|
+
* algo del disco de alguien a quien no le hemos preguntado.
|
|
118
|
+
*/
|
|
119
|
+
export function rutaServible(cwd, pedida) {
|
|
120
|
+
if (!pedida)
|
|
121
|
+
return null;
|
|
122
|
+
try {
|
|
123
|
+
const absoluta = path.isAbsolute(pedida) ? pedida : path.resolve(cwd, pedida);
|
|
124
|
+
// realpath ANTES de comparar: si no, un enlace simbolico dentro de la
|
|
125
|
+
// carpeta apuntando a C:\Users\... pasaria la comprobacion y serviria
|
|
126
|
+
// cualquier cosa del disco.
|
|
127
|
+
const real = fs.realpathSync(absoluta);
|
|
128
|
+
const raiz = fs.realpathSync(cwd);
|
|
129
|
+
const dentro = real === raiz || real.startsWith(raiz + path.sep);
|
|
130
|
+
if (!dentro)
|
|
131
|
+
return null;
|
|
132
|
+
if (!fs.statSync(real).isFile())
|
|
133
|
+
return null;
|
|
134
|
+
return real;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* La linea que se le mete a la conversacion cuando llegan archivos.
|
|
142
|
+
*
|
|
143
|
+
* Lleva las rutas COMPLETAS y no los nombres: el modelo tiene que poder
|
|
144
|
+
* abrirlos sin adivinar donde cayeron, y "la foto que acabo de subir" no es una
|
|
145
|
+
* ruta. Y se dice que vienen del navegador, porque cambia lo que hay que hacer
|
|
146
|
+
* con ellos: no son archivos del proyecto, son material que acaba de llegar.
|
|
147
|
+
*/
|
|
148
|
+
export function avisoDeSubida(rutas) {
|
|
149
|
+
if (rutas.length === 0)
|
|
150
|
+
return "";
|
|
151
|
+
if (rutas.length === 1)
|
|
152
|
+
return `[El usuario subio un archivo desde el navegador: ${rutas[0]}]`;
|
|
153
|
+
return (`[El usuario subio ${rutas.length} archivos desde el navegador:\n` +
|
|
154
|
+
rutas.map((r) => `- ${r}`).join("\n") +
|
|
155
|
+
"]");
|
|
156
|
+
}
|
|
@@ -28,6 +28,15 @@ export interface LineaWeb {
|
|
|
28
28
|
}
|
|
29
29
|
/** Mete una linea propia (avisos del servidor, eco de lo que llega del movil). */
|
|
30
30
|
export declare function anotarWeb(texto: string): void;
|
|
31
|
+
export declare function anotarCrudoWeb(texto: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Suelta lo que quede sin salto de linea.
|
|
34
|
+
*
|
|
35
|
+
* Hace falta porque la ultima linea de una respuesta muchas veces no lo trae, y
|
|
36
|
+
* sin esto se quedaria esperando un \n que no va a llegar: el final de cada
|
|
37
|
+
* respuesta desapareceria del movil.
|
|
38
|
+
*/
|
|
39
|
+
export declare function cerrarLineaWeb(): void;
|
|
31
40
|
export declare function iniciarCaptura(): void;
|
|
32
41
|
export declare function detenerCaptura(): void;
|
|
33
42
|
export declare function historialWeb(desde?: number): LineaWeb[];
|