chocolatito-code 1.6.12 → 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/agent/cortes.d.ts +41 -0
- package/dist/agent/cortes.js +52 -0
- package/dist/agent/loop.js +35 -0
- package/dist/config/engine.js +7 -0
- package/dist/config/permissions.js +4 -0
- package/dist/index.js +147 -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/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
|
@@ -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
|
+
}>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
export function rutaDeLaSesion(home = os.homedir()) {
|
|
6
|
+
return path.join(home, ".chocolatito", "sesion-web.json");
|
|
7
|
+
}
|
|
8
|
+
export function guardarSesion(datos, home) {
|
|
9
|
+
const ruta = rutaDeLaSesion(home);
|
|
10
|
+
fs.mkdirSync(path.dirname(ruta), { recursive: true });
|
|
11
|
+
fs.writeFileSync(ruta, JSON.stringify(datos, null, 2), "utf-8");
|
|
12
|
+
}
|
|
13
|
+
export function olvidarSesion(home) {
|
|
14
|
+
try {
|
|
15
|
+
fs.rmSync(rutaDeLaSesion(home), { force: true });
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Si un proceso existe.
|
|
21
|
+
*
|
|
22
|
+
* `process.kill(pid, 0)` no mata nada: pregunta. Devuelve sin error si el
|
|
23
|
+
* proceso esta ahi, ESRCH si no existe, y EPERM si existe pero es de otro
|
|
24
|
+
* usuario -que para lo que nos importa tambien es "existe"-.
|
|
25
|
+
*/
|
|
26
|
+
export function procesoVivo(pid, matar = process.kill) {
|
|
27
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
28
|
+
return false;
|
|
29
|
+
try {
|
|
30
|
+
matar(pid, 0);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return err?.code === "EPERM";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** La sesion de fondo, solo si de verdad sigue viva. */
|
|
38
|
+
export function sesionViva(home, vivo = (p) => procesoVivo(p)) {
|
|
39
|
+
try {
|
|
40
|
+
const datos = JSON.parse(fs.readFileSync(rutaDeLaSesion(home), "utf-8"));
|
|
41
|
+
if (!datos?.pid || !vivo(datos.pid)) {
|
|
42
|
+
// El archivo sobrevivio al proceso. Se limpia aqui para que el siguiente
|
|
43
|
+
// arranque no tenga que decidir otra vez sobre un fantasma.
|
|
44
|
+
olvidarSesion(home);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return datos;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Se relanza a si mismo, suelto del terminal.
|
|
55
|
+
*
|
|
56
|
+
* `detached` + `stdio: ignore` + `unref` son las tres piezas, y las tres hacen
|
|
57
|
+
* falta: sin detached el hijo muere con el padre en Unix; sin ignorar la salida
|
|
58
|
+
* el hijo se bloquea en cuanto llene el buffer de una tuberia que ya no lee
|
|
59
|
+
* nadie; y sin unref el padre se queda esperando al hijo y el terminal no vuelve.
|
|
60
|
+
*/
|
|
61
|
+
export function lanzarEnFondo(argumentos, opciones = {}) {
|
|
62
|
+
const ejecutable = opciones.ejecutable || process.execPath;
|
|
63
|
+
const guion = opciones.guion || process.argv[1];
|
|
64
|
+
const hijo = spawn(ejecutable, [guion, ...argumentos], {
|
|
65
|
+
cwd: opciones.cwd || process.cwd(),
|
|
66
|
+
detached: true,
|
|
67
|
+
stdio: "ignore",
|
|
68
|
+
windowsHide: true,
|
|
69
|
+
env: { ...process.env, CHOCOLATITO_EN_FONDO: "1" },
|
|
70
|
+
});
|
|
71
|
+
hijo.unref();
|
|
72
|
+
return hijo.pid ?? 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Para la sesion de fondo. Devuelve que se hizo, en palabras.
|
|
76
|
+
*
|
|
77
|
+
* En Windows se mata el arbol entero con taskkill /T: el proceso de Node puede
|
|
78
|
+
* tener debajo un powershell.exe del control del ordenador o un Chrome del
|
|
79
|
+
* navegador, y matar solo al padre los deja huerfanos comiendo memoria.
|
|
80
|
+
*/
|
|
81
|
+
export async function pararSesion(home, opciones = {}) {
|
|
82
|
+
// Quien decide si un PID vive se inyecta ARRIBA y se usa en los dos sitios:
|
|
83
|
+
// al leer la ficha y al comprobar despues. Con la comprobacion de verdad en la
|
|
84
|
+
// primera y la inyectada en la segunda, la funcion contestaba una cosa u otra
|
|
85
|
+
// segun si ese numero de proceso existia por casualidad en la maquina que
|
|
86
|
+
// corriera las pruebas.
|
|
87
|
+
const vivo = opciones.vivo || ((pid) => procesoVivo(pid));
|
|
88
|
+
const esWindows = opciones.esWindows ?? process.platform === "win32";
|
|
89
|
+
const sesion = sesionViva(home, vivo);
|
|
90
|
+
if (!sesion) {
|
|
91
|
+
return { ok: false, mensaje: "No hay ninguna sesion de fondo abierta." };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (esWindows) {
|
|
95
|
+
// spawnSync Y NO spawn. La primera version lanzaba taskkill sin esperarlo
|
|
96
|
+
// y salia con process.exit() en la linea siguiente: el hijo no llegaba a
|
|
97
|
+
// arrancar, el archivo de sesion se borraba, y el programa anunciaba
|
|
98
|
+
// "Sesion detenida" con la sesion contestando en el puerto tan tranquila.
|
|
99
|
+
// Se vio en la prueba de verdad: --parar decia que si, y la pagina seguia
|
|
100
|
+
// dando 200.
|
|
101
|
+
const ejecutar = opciones.ejecutar ||
|
|
102
|
+
((cmd, args) => {
|
|
103
|
+
const r = spawnSync(cmd, args, { windowsHide: true, encoding: "utf-8" });
|
|
104
|
+
return { ok: r.status === 0, detalle: String(r.stderr || r.stdout || "").trim() };
|
|
105
|
+
});
|
|
106
|
+
// /T mata el arbol: debajo puede haber un powershell del control del
|
|
107
|
+
// ordenador o un Chrome del navegador, y matar solo al padre los deja
|
|
108
|
+
// huerfanos comiendo memoria.
|
|
109
|
+
ejecutar("taskkill", ["/pid", String(sesion.pid), "/T", "/F"]);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const matar = opciones.matar || ((pid) => process.kill(-pid, "SIGTERM"));
|
|
113
|
+
matar(sesion.pid);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Si ya no estaba, el resultado es el que buscabamos. Se comprueba abajo.
|
|
118
|
+
}
|
|
119
|
+
// No se cree lo que diga el comando: se mira si el proceso sigue ahi. Es la
|
|
120
|
+
// unica forma de no volver a mentir.
|
|
121
|
+
for (let intento = 0; intento < 20 && vivo(sesion.pid); intento++) {
|
|
122
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
123
|
+
}
|
|
124
|
+
if (vivo(sesion.pid)) {
|
|
125
|
+
// El archivo se queda: la sesion existe, asi que --estado tiene que seguir
|
|
126
|
+
// encontrandola para poder intentarlo otra vez o matarla a mano.
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
mensaje: `No se pudo detener la sesion (pid ${sesion.pid}). Sigue abierta en ${sesion.url}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
olvidarSesion(home);
|
|
133
|
+
return { ok: true, mensaje: `Sesion de fondo detenida (pid ${sesion.pid}).` };
|
|
134
|
+
}
|