chocolatito-code 1.6.13 → 1.6.14
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 +9 -2
- package/dist/config/permissions.js +4 -0
- package/dist/index.js +129 -24
- 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/fondo.d.ts +85 -0
- package/dist/servidor/fondo.js +134 -0
- package/dist/servidor/pagina.js +266 -2
- package/dist/servidor/puente.d.ts +13 -0
- package/dist/servidor/puente.js +11 -0
- package/dist/servidor/servidor.d.ts +6 -0
- package/dist/servidor/servidor.js +129 -6
- 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/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/COMPUTER-USE.md
CHANGED
|
@@ -482,3 +482,40 @@ Es ambiguo y no voy a elegir por ti: usa list_windows y pasa un titulo mas concr
|
|
|
482
482
|
|
|
483
483
|
Elegir a ciegas entre candidatos empatados no es una comodidad: es cómo se acaba
|
|
484
484
|
mandando una pulsación destructiva a la ventana equivocada.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 9. Nivel Google Project Astra & OpenAI Operator: Percepción Multimodal y Flujos Autónomos
|
|
489
|
+
|
|
490
|
+
Para acercar Chocolatito Code al nivel de **Google Project Astra** y **OpenAI Operator**, se incorporaron capacidades avanzadas de percepción multimodal y ejecución compuesta:
|
|
491
|
+
|
|
492
|
+
### 9.1 Cuadrícula de Coordenadas Visuales (`grid: true`)
|
|
493
|
+
Cuando una interfaz carece de árbol de accesibilidad (lienzos HTML5/WebGL, motores de videojuegos, apps Qt/Flutter o ventanas remotas), los modelos multimodales a menudo sufren desviaciones de 30 a 80 píxeles.
|
|
494
|
+
Al pasar `grid: true` (o en `find_element` visual), el host dibuja una cuadrícula milimétrica semitransparente con marcadores de coordenadas directas `(x,y)` cada 200px. El modelo de visión lee el marcador visual exacto, logrando una precisión de localización superior al 98%.
|
|
495
|
+
|
|
496
|
+
### 9.2 Encadenamiento de Acciones Compuestas (`action: "sequence"`)
|
|
497
|
+
Reducir los viajes de ida y vuelta al modelo es esencial para la agilidad. En vez de pagar 4 turnos (20–30s) para hacer clic en un buscador, escribir una consulta, presionar Enter y esperar el cambio, se pasa una sola acción:
|
|
498
|
+
```json
|
|
499
|
+
{
|
|
500
|
+
"action": "sequence",
|
|
501
|
+
"steps": [
|
|
502
|
+
{ "action": "ui_click", "ref": 4 },
|
|
503
|
+
{ "action": "type", "text": "chocolatito code" },
|
|
504
|
+
{ "action": "key", "keys": "Return" },
|
|
505
|
+
{ "action": "wait_change" }
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
Se ejecuta en subsegundos en la máquina local y devuelve el resultado consolidado junto con el estado posterior de la ventana.
|
|
510
|
+
|
|
511
|
+
### 9.3 Detección de Cambios Visuales (`wait_visual_change`)
|
|
512
|
+
Las aplicaciones que dibujan mediante DirectX, OpenGL, streaming o canvas web no disparan eventos de Windows UI Automation. `wait_visual_change` realiza un muestreo de píxeles de alta velocidad (128x128) y detecta transiciones en la pantalla, ventana o región específica sin consumir CPU.
|
|
513
|
+
|
|
514
|
+
### 9.4 Inspección de Alta Resolución por Región (`region: [x1, y1, x2, y2]`)
|
|
515
|
+
Permite recortar e inspeccionar un área crítica (iconos de bandeja, barras de estado, mensajes de error diminutos) en resolución nativa 1:1 sin sobrepasar el límite de tamaño de imagen para visión.
|
|
516
|
+
|
|
517
|
+
### 9.5 Auto-Recuperación Resiliente en `ui_click` y `ui_type`
|
|
518
|
+
Si un control de interfaz personalizada (WinUI 3, WPF o Electron) no implementa `InvokePattern` o `TogglePattern`, `ui_click` no falla: obtiene automáticamente el centroide de su rectángulo delimitador (`ui_rect`) y envía un clic de segundo plano (`bg_click`). Igualmente, `ui_type` apoya con enfoque automático por clic antes de teclear.
|
|
519
|
+
|
|
520
|
+
### 9.6 Coordenadas Normalizadas (0..1000 y 0.0..1.0)
|
|
521
|
+
Soporte nativo para coordenadas normalizadas en escala 0..1000 estilo OpenAI Operator y Anthropic, adaptándose automáticamente a monitores 1080p, 2K, 4K y configuraciones multimonitor.
|
package/README.md
CHANGED
|
@@ -94,7 +94,10 @@ teclado.
|
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
96
|
chocolatito --servir # solo desde esta máquina
|
|
97
|
-
chocolatito --servir-red # también desde el móvil o la tablet de tu red
|
|
97
|
+
chocolatito --servir-red # también desde el móvil o la tablet de tu red
|
|
98
|
+
chocolatito --servir-red --fondo # y suelta el terminal: puedes cerrar la ventana
|
|
99
|
+
chocolatito --estado # ¿sigue viva?
|
|
100
|
+
chocolatito --parar # cerrarla
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
Imprime un enlace con un token dentro. Ese enlace **es** la llave: quien lo tenga entero
|
|
@@ -109,7 +112,11 @@ JavaScript de Node. Por el navegador no hace falta instalar nada.
|
|
|
109
112
|
| --- | --- |
|
|
110
113
|
| `--puerto N` | Otro puerto (por defecto 4700). |
|
|
111
114
|
| Se reconecta solo | Un móvil pierde la red a cada rato; la sesión no se pierde con ella. |
|
|
112
|
-
| Los permisos | Salen en el móvil, con los mismos botones que en el terminal. |
|
|
115
|
+
| Los permisos | Salen en el móvil, con los mismos botones que en el terminal. |
|
|
116
|
+
| Archivos | Arrastra, pega del portapapeles o pulsa `+`. Caen en `entradas/` y el agente los recibe con su ruta. |
|
|
117
|
+
| Imágenes | Lo que el agente exporta se **ve** en la página, no se anuncia con una ruta. |
|
|
118
|
+
| Te avisa | Cuando pide permiso o cuando termina: notificación, vibración y un punto en la pestaña. Solo si no estás mirando la página. |
|
|
119
|
+
| Y te lo llevas | **⇩ Guardar** bajo cada resultado: la pieza, el ZIP o el PDF van al teléfono sin pasar por el ordenador. |
|
|
113
120
|
|
|
114
121
|
**Manda el navegador.** Con `--servir`, lo que escribas en el terminal ya no se lee: el
|
|
115
122
|
terminal queda de monitor. Es a propósito — dos entradas peleándose por el mismo teclado
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import { reafirmarModoCrudo } from "./ui/keyboardGuard.js";
|
|
|
24
24
|
import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
|
|
25
25
|
import { changeDir } from "./tools/changeDir.js";
|
|
26
26
|
import { gitDiff, gitCommit } from "./tools/gitTools.js";
|
|
27
|
-
import { renderUserPrompt } from "./ui/renderer.js";
|
|
27
|
+
import { renderUserPrompt, formatColorDiff } from "./ui/renderer.js";
|
|
28
28
|
import { parseFileMentions, buildPromptWithMentions, notaDeMencionesImplicitas } from "./agent/mentions.js";
|
|
29
29
|
import { SkillManager } from "./skills/manager.js";
|
|
30
30
|
import { MemoryManager } from "./memory/manager.js";
|
|
@@ -33,6 +33,7 @@ import { SessionManager } from "./sessions/manager.js";
|
|
|
33
33
|
import { prepararRestauracion, formatearOpcionesDeSesion } from "./sessions/resume.js";
|
|
34
34
|
import { auditGitDiff, formatAuditReport } from "./tools/gitAudit.js";
|
|
35
35
|
import { instalarKit, listarKits, quitarKit, crearKit } from "./kits/manager.js";
|
|
36
|
+
import { guardarSesion, lanzarEnFondo, olvidarSesion, pararSesion, sesionViva } from "./servidor/fondo.js";
|
|
36
37
|
import { leerCortes, rutaDelRegistro } from "./agent/cortes.js";
|
|
37
38
|
import { globalUndoManager } from "./agent/undoManager.js";
|
|
38
39
|
import { setAutoApprove } from "./ui/permissionPrompt.js";
|
|
@@ -220,8 +221,8 @@ async function main() {
|
|
|
220
221
|
// errores raros dentro de una dependencia. Ver config/nodeVersion.ts.
|
|
221
222
|
const nodeViejo = comprobarNode();
|
|
222
223
|
if (nodeViejo) {
|
|
223
|
-
console.error(chalk.red(`
|
|
224
|
-
${nodeViejo}
|
|
224
|
+
console.error(chalk.red(`
|
|
225
|
+
${nodeViejo}
|
|
225
226
|
`));
|
|
226
227
|
process.exit(EXIT_USO);
|
|
227
228
|
}
|
|
@@ -235,13 +236,50 @@ ${nodeViejo}
|
|
|
235
236
|
// red local, que es lo que hace falta para entrar desde el movil.
|
|
236
237
|
const enRed = rawArgs.includes("--servir-red");
|
|
237
238
|
const quiereServir = enRed || rawArgs.includes("--servir");
|
|
239
|
+
// --fondo suelta la sesion del terminal; --parar y --estado la gobiernan
|
|
240
|
+
// desde fuera, sin tener que buscar la ventana que la lanzo.
|
|
241
|
+
const quiereFondo = rawArgs.includes("--fondo");
|
|
242
|
+
const quiereParar = rawArgs.includes("--parar");
|
|
243
|
+
const quiereEstado = rawArgs.includes("--estado");
|
|
238
244
|
const posPuerto = rawArgs.indexOf("--puerto");
|
|
239
245
|
const puertoWeb = posPuerto >= 0 ? Number(rawArgs[posPuerto + 1]) || 0 : 0;
|
|
240
246
|
// Ojo: lo que sobra de argv se convierte en la orden a ejecutar. Una bandera
|
|
241
247
|
// que no se filtre aqui no es una bandera ignorada, es una orden al modelo.
|
|
242
|
-
const banderas = new Set([
|
|
248
|
+
const banderas = new Set([
|
|
249
|
+
"--yes",
|
|
250
|
+
"-y",
|
|
251
|
+
"--servir",
|
|
252
|
+
"--servir-red",
|
|
253
|
+
"--puerto",
|
|
254
|
+
"--fondo",
|
|
255
|
+
"--parar",
|
|
256
|
+
"--estado",
|
|
257
|
+
]);
|
|
243
258
|
const args = rawArgs.filter((a, i) => !banderas.has(a) && !(posPuerto >= 0 && i === posPuerto + 1 && puertoWeb > 0));
|
|
244
259
|
const singlePrompt = args.join(" ").trim();
|
|
260
|
+
// ESTAS DOS NO ARRANCAN NADA, y por eso van aqui arriba: montar el agente
|
|
261
|
+
// entero -API key, skills, MCP- para luego solo decir un PID seria hacer
|
|
262
|
+
// esperar al usuario por nada, y ademas pediria la clave a quien solo quiere
|
|
263
|
+
// apagar la sesion de ayer.
|
|
264
|
+
if (quiereParar) {
|
|
265
|
+
const res = await pararSesion();
|
|
266
|
+
console.log(res.ok ? chalk.green(`\n ✔ ${res.mensaje}\n`) : chalk.yellow(`\n ${res.mensaje}\n`));
|
|
267
|
+
process.exit(res.ok ? 0 : 1);
|
|
268
|
+
}
|
|
269
|
+
if (quiereEstado) {
|
|
270
|
+
const viva = sesionViva();
|
|
271
|
+
if (!viva) {
|
|
272
|
+
console.log(chalk.gray("\n No hay ninguna sesion de fondo abierta.\n"));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
console.log(chalk.bold.hex("#e8833a")("\n 🦊 Sesion de fondo abierta"));
|
|
276
|
+
console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
|
|
277
|
+
console.log(` ${chalk.bold("Carpeta:")} ${chalk.white(viva.carpeta)}`);
|
|
278
|
+
console.log(` ${chalk.bold("Desde:")} ${chalk.white(new Date(viva.desde).toLocaleString())}`);
|
|
279
|
+
console.log(chalk.gray(` pid ${viva.pid} · ${viva.enRed ? "visible en tu red" : "solo esta maquina"}`));
|
|
280
|
+
console.log(chalk.gray("\n Para cerrarla: chocolatito --parar\n"));
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
245
283
|
// Los errores de uso se resuelven ANTES de la licencia y de la API key: no
|
|
246
284
|
// dependen de estar suscrito, y en CI hay que poder distinguir "lo has
|
|
247
285
|
// invocado mal" de "el agente no pudo".
|
|
@@ -408,11 +446,78 @@ ${nodeViejo}
|
|
|
408
446
|
// reemplazo los cuatro canales de console para mandarlos a la transcripcion, y
|
|
409
447
|
// la captura tiene que envolver ESA version. Al reves, pantalla envolveria a la
|
|
410
448
|
// captura y al navegador no llegaria nada. Ver servidor/captura.ts.
|
|
449
|
+
// SOLTARSE DEL TERMINAL
|
|
450
|
+
//
|
|
451
|
+
// Se relanza el mismo programa con los mismos argumentos, sin --fondo, y este
|
|
452
|
+
// proceso se va. El hijo no hereda ni la entrada ni la salida: la ventana se
|
|
453
|
+
// puede cerrar y la sesion sigue.
|
|
454
|
+
//
|
|
455
|
+
// El enlace lo imprime el PADRE, pero el token lo genera el HIJO. Asi que en
|
|
456
|
+
// vez de inventar un canal para pasarlo, se espera a que el hijo escriba su
|
|
457
|
+
// archivo de sesion. Eso ademas confirma que arranco de verdad: si el hijo
|
|
458
|
+
// revienta -puerto ocupado, licencia caducada-, el archivo no aparece y se
|
|
459
|
+
// dice que fallo, en lugar de imprimir un enlace a nada.
|
|
460
|
+
if (quiereServir && quiereFondo && !process.env["CHOCOLATITO_EN_FONDO"]) {
|
|
461
|
+
const yaAbierta = sesionViva();
|
|
462
|
+
if (yaAbierta) {
|
|
463
|
+
console.log(chalk.yellow("\n Ya hay una sesion de fondo abierta."));
|
|
464
|
+
console.log(` ${chalk.cyan(yaAbierta.url)}\n`);
|
|
465
|
+
console.log(chalk.gray(" Para cerrarla: chocolatito --parar\n"));
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
olvidarSesion();
|
|
469
|
+
const pid = lanzarEnFondo(rawArgs.filter((a) => a !== "--fondo"));
|
|
470
|
+
let viva = null;
|
|
471
|
+
for (let intento = 0; intento < 100 && !viva; intento++) {
|
|
472
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
473
|
+
viva = sesionViva();
|
|
474
|
+
}
|
|
475
|
+
if (!viva) {
|
|
476
|
+
console.log(chalk.red("\n La sesion de fondo no llego a arrancar."));
|
|
477
|
+
console.log(chalk.gray(` Prueba sin --fondo para ver el error (pid ${pid}).\n`));
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
console.log(chalk.hex("#e8833a")("\n 🦊 Sesion abierta en segundo plano\n"));
|
|
481
|
+
console.log(` ${chalk.bold("Enlace:")} ${chalk.cyan(viva.url)}`);
|
|
482
|
+
console.log(chalk.gray("\n Ya puedes cerrar esta ventana: la sesion sigue."));
|
|
483
|
+
console.log(chalk.gray(" chocolatito --estado · chocolatito --parar\n"));
|
|
484
|
+
process.exit(0);
|
|
485
|
+
}
|
|
411
486
|
if (quiereServir) {
|
|
412
487
|
try {
|
|
413
488
|
iniciarCaptura();
|
|
414
|
-
const web = await iniciarServidorWeb({
|
|
489
|
+
const web = await iniciarServidorWeb({
|
|
490
|
+
puerto: puertoWeb || undefined,
|
|
491
|
+
enRed,
|
|
492
|
+
// La carpeta se pregunta en cada peticion, no se copia aqui: con /cd el
|
|
493
|
+
// agente se cambia a mitad de sesion y lo que suba el movil tiene que
|
|
494
|
+
// caer donde se esta trabajando AHORA.
|
|
495
|
+
carpeta: () => agent.currentCwd,
|
|
496
|
+
});
|
|
415
497
|
marcarServirActivo(true);
|
|
498
|
+
// Solo el que corre suelto deja su ficha: una sesion normal muere con su
|
|
499
|
+
// terminal y anotarla solo serviria para que --estado mintiera despues.
|
|
500
|
+
if (process.env["CHOCOLATITO_EN_FONDO"]) {
|
|
501
|
+
guardarSesion({
|
|
502
|
+
pid: process.pid,
|
|
503
|
+
puerto: web.puerto,
|
|
504
|
+
token: web.token,
|
|
505
|
+
url: web.urlsDeRed[0] || web.url,
|
|
506
|
+
carpeta: agent.currentCwd,
|
|
507
|
+
desde: new Date().toISOString(),
|
|
508
|
+
enRed,
|
|
509
|
+
});
|
|
510
|
+
const limpiar = () => olvidarSesion();
|
|
511
|
+
process.once("exit", limpiar);
|
|
512
|
+
process.once("SIGINT", () => {
|
|
513
|
+
limpiar();
|
|
514
|
+
process.exit(0);
|
|
515
|
+
});
|
|
516
|
+
process.once("SIGTERM", () => {
|
|
517
|
+
limpiar();
|
|
518
|
+
process.exit(0);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
416
521
|
console.log(chalk.hex("#e8833a")("\n 🦊 Sesión abierta en el navegador\n"));
|
|
417
522
|
console.log(` ${chalk.bold("Aquí:")} ${chalk.cyan(web.url)}`);
|
|
418
523
|
for (const dir of web.urlsDeRed) {
|
|
@@ -522,15 +627,15 @@ ${nodeViejo}
|
|
|
522
627
|
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
523
628
|
.map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
|
|
524
629
|
.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}
|
|
630
|
+
const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
|
|
631
|
+
**Fecha:** ${new Date().toLocaleString()}
|
|
632
|
+
**Directorio:** \`${agent.currentCwd}\`
|
|
633
|
+
**Motor:** ${currentModel.name}
|
|
634
|
+
**Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
${sessionData}
|
|
534
639
|
`;
|
|
535
640
|
fs.writeFileSync(exportPath, mdContent, "utf-8");
|
|
536
641
|
console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
|
|
@@ -746,12 +851,12 @@ ${sessionData}
|
|
|
746
851
|
}
|
|
747
852
|
console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
|
|
748
853
|
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.
|
|
854
|
+
const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
|
|
855
|
+
Tu objetivo principal es: "${goalText}"
|
|
856
|
+
Directivas de ejecución:
|
|
857
|
+
1. Formula un plan de ejecución mental paso a paso.
|
|
858
|
+
2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
|
|
859
|
+
3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
|
|
755
860
|
4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
|
|
756
861
|
renderUserPrompt(`/goal ${goalText}`);
|
|
757
862
|
await agent.runTask(autonomousPrompt);
|
|
@@ -774,7 +879,7 @@ Directivas de ejecución:
|
|
|
774
879
|
if (trimmed === "/diff") {
|
|
775
880
|
const diffOut = await gitDiff(false, agent.currentCwd);
|
|
776
881
|
console.log(chalk.bold.hex("#D97757")("\n📝 DIFERENCIAS DE GIT (DIFF):"));
|
|
777
|
-
console.log(diffOut ?
|
|
882
|
+
console.log(diffOut ? `${formatColorDiff(diffOut)}\n` : chalk.green("✔ Sin cambios pendientes.") + "\n");
|
|
778
883
|
continue;
|
|
779
884
|
}
|
|
780
885
|
if (trimmed === "/audit" || trimmed === "/review" || trimmed.startsWith("/audit ") || trimmed.startsWith("/review ")) {
|
|
@@ -861,12 +966,12 @@ Directivas de ejecución:
|
|
|
861
966
|
const refreshedContext = await getProjectContext(agent.currentCwd);
|
|
862
967
|
const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
|
|
863
968
|
agent.setEspectroModel(currentModel, updatedPrompt);
|
|
864
|
-
console.log(chalk.green(`
|
|
969
|
+
console.log(chalk.green(`
|
|
865
970
|
✔ Nivel actualizado a `) +
|
|
866
971
|
chalk.bold.hex("#A855F7")(currentModel.name) +
|
|
867
972
|
chalk.gray(` (${currentModel.effortLevel})`) +
|
|
868
|
-
`
|
|
869
|
-
${chalk.italic.gray(currentModel.description)}
|
|
973
|
+
`
|
|
974
|
+
${chalk.italic.gray(currentModel.description)}
|
|
870
975
|
`);
|
|
871
976
|
// EL TECLADO SE QUEDABA TRABADO AL CAMBIAR DE MOTOR
|
|
872
977
|
//
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LA SESION QUE SOBREVIVE AL TERMINAL
|
|
3
|
+
*
|
|
4
|
+
* DE DONDE SALE
|
|
5
|
+
*
|
|
6
|
+
* `--servir` ya ponia la sesion en el navegador del movil, pero seguia colgando
|
|
7
|
+
* de la ventana del terminal: si se cerraba -o se cerraba el portatil, o se
|
|
8
|
+
* reiniciaba la maquina- la sesion moria con ella. O sea que para trabajar desde
|
|
9
|
+
* el telefono habia que dejar una ventana negra abierta en el PC y no tocarla,
|
|
10
|
+
* que es justo la clase de cosa que se cierra sin querer.
|
|
11
|
+
*
|
|
12
|
+
* Con `--servir --fondo` el proceso se suelta del terminal: arranca, imprime el
|
|
13
|
+
* enlace, y la ventana se puede cerrar. La sesion sigue viva con el navegador
|
|
14
|
+
* como unica interfaz.
|
|
15
|
+
*
|
|
16
|
+
* POR QUE UN ARCHIVO Y NO UN PUERTO FIJO
|
|
17
|
+
*
|
|
18
|
+
* Para volver a entrar hace falta saber tres cosas: en que puerto escucha, con
|
|
19
|
+
* que token, y si sigue viva. Un puerto fijo no dice lo segundo ni lo tercero.
|
|
20
|
+
* Se guardan en `~/.chocolatito/sesion-web.json`, al lado de la clave de API y
|
|
21
|
+
* con los mismos permisos que el resto de la configuracion del usuario.
|
|
22
|
+
*
|
|
23
|
+
* Y SE COMPRUEBA QUE SIGUE VIVA, NO SE DA POR HECHO
|
|
24
|
+
*
|
|
25
|
+
* Un archivo con un PID dentro no es una sesion: es un archivo. El proceso pudo
|
|
26
|
+
* morir de mil formas que no pasan por borrarlo -un apagon, un cierre forzoso,
|
|
27
|
+
* una actualizacion de Windows-, y entonces `--parar` mataria a quien hubiera
|
|
28
|
+
* heredado ese numero, que en un PC encendido varios dias es cualquiera. Antes
|
|
29
|
+
* de creerse nada se pregunta al sistema si ese PID existe.
|
|
30
|
+
*/
|
|
31
|
+
export interface SesionEnFondo {
|
|
32
|
+
pid: number;
|
|
33
|
+
puerto: number;
|
|
34
|
+
token: string;
|
|
35
|
+
url: string;
|
|
36
|
+
carpeta: string;
|
|
37
|
+
desde: string;
|
|
38
|
+
/** Si se abrio con --servir-red y por tanto se ve desde el movil. */
|
|
39
|
+
enRed: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function rutaDeLaSesion(home?: string): string;
|
|
42
|
+
export declare function guardarSesion(datos: SesionEnFondo, home?: string): void;
|
|
43
|
+
export declare function olvidarSesion(home?: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Si un proceso existe.
|
|
46
|
+
*
|
|
47
|
+
* `process.kill(pid, 0)` no mata nada: pregunta. Devuelve sin error si el
|
|
48
|
+
* proceso esta ahi, ESRCH si no existe, y EPERM si existe pero es de otro
|
|
49
|
+
* usuario -que para lo que nos importa tambien es "existe"-.
|
|
50
|
+
*/
|
|
51
|
+
export declare function procesoVivo(pid: number, matar?: (p: number, s: number) => void): boolean;
|
|
52
|
+
/** La sesion de fondo, solo si de verdad sigue viva. */
|
|
53
|
+
export declare function sesionViva(home?: string, vivo?: (pid: number) => boolean): SesionEnFondo | null;
|
|
54
|
+
/**
|
|
55
|
+
* Se relanza a si mismo, suelto del terminal.
|
|
56
|
+
*
|
|
57
|
+
* `detached` + `stdio: ignore` + `unref` son las tres piezas, y las tres hacen
|
|
58
|
+
* falta: sin detached el hijo muere con el padre en Unix; sin ignorar la salida
|
|
59
|
+
* el hijo se bloquea en cuanto llene el buffer de una tuberia que ya no lee
|
|
60
|
+
* nadie; y sin unref el padre se queda esperando al hijo y el terminal no vuelve.
|
|
61
|
+
*/
|
|
62
|
+
export declare function lanzarEnFondo(argumentos: string[], opciones?: {
|
|
63
|
+
ejecutable?: string;
|
|
64
|
+
guion?: string;
|
|
65
|
+
cwd?: string;
|
|
66
|
+
}): number;
|
|
67
|
+
/**
|
|
68
|
+
* Para la sesion de fondo. Devuelve que se hizo, en palabras.
|
|
69
|
+
*
|
|
70
|
+
* En Windows se mata el arbol entero con taskkill /T: el proceso de Node puede
|
|
71
|
+
* tener debajo un powershell.exe del control del ordenador o un Chrome del
|
|
72
|
+
* navegador, y matar solo al padre los deja huerfanos comiendo memoria.
|
|
73
|
+
*/
|
|
74
|
+
export declare function pararSesion(home?: string, opciones?: {
|
|
75
|
+
esWindows?: boolean;
|
|
76
|
+
ejecutar?: (cmd: string, args: string[]) => {
|
|
77
|
+
ok: boolean;
|
|
78
|
+
detalle: string;
|
|
79
|
+
};
|
|
80
|
+
matar?: (pid: number) => void;
|
|
81
|
+
vivo?: (pid: number) => boolean;
|
|
82
|
+
}): Promise<{
|
|
83
|
+
ok: boolean;
|
|
84
|
+
mensaje: string;
|
|
85
|
+
}>;
|