@danhachuel/leadmaster 0.1.8 → 0.1.9
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/CHANGELOG.md +26 -0
- package/backend/__pycache__/main.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/__init__.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/agents.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/crm_agent.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/extractors.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/flags.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/llm.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/storage.cpython-311.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_crm_routes.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_launcher_protections.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_process_flags.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/test_launcher_protections.py +46 -0
- package/package.json +1 -1
- package/scripts/cli.mjs +70 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@ O formato é baseado no [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.
|
|
|
6
6
|
A etiqueta visual de versão usa patch de dois dígitos (ex.: `0.1.00`); o registry NPM
|
|
7
7
|
normaliza para a forma canônica (ex.: `0.1.0`).
|
|
8
8
|
|
|
9
|
+
## [0.1.09] — 2026-11-08
|
|
10
|
+
|
|
11
|
+
### Corrigido
|
|
12
|
+
|
|
13
|
+
- **Encerramento silencioso do launcher no MobaXterm (Windows)** — o app morria
|
|
14
|
+
sozinho por sinais espúrios (perda de foco/minimização) e por EPIPE ao fechar o
|
|
15
|
+
terminal. Correções no `scripts/cli.mjs`:
|
|
16
|
+
- **Keep-alive:** `setInterval(() => {}, 1000);` mantém o event loop ativo mesmo
|
|
17
|
+
sem I/O de console.
|
|
18
|
+
- **Absorção de sinais no Windows:** `SIGINT`/`SIGHUP` agora são apenas logados
|
|
19
|
+
(`[LAUNCHER] Sinal absorvido no Windows`) e **não** encerram o launcher;
|
|
20
|
+
`SIGTERM`/`SIGBREAK` (Ctrl+Break) seguem como paradas legítimas. No
|
|
21
|
+
Linux/macOS o comportamento gracioso é mantido.
|
|
22
|
+
- **Guarda da consola (anti-EPIPE):** `guardConsoleStream()` ignora erros `EPIPE`
|
|
23
|
+
no `'error'` dos streams e espelha toda a saída em
|
|
24
|
+
`storage/logs/launcher_console.log`; escrita original com exceção retorna
|
|
25
|
+
silenciosamente. Aplicada a `process.stdout`/`process.stderr` antes de
|
|
26
|
+
qualquer escrita do launcher.
|
|
27
|
+
- Isolamento mantido: worker Python com `detached: true` + `windowsHide: true`
|
|
28
|
+
(`CREATE_NEW_PROCESS_GROUP`).
|
|
29
|
+
- **AGENTS.md:** nova seção *🚨 REGRA INVIOLÁVEL: ARQUITETURA DO LAUNCHER* com as
|
|
30
|
+
4 proteções obrigatórias.
|
|
31
|
+
- **Gate de regressão:** `backend/tests/test_launcher_protections.py` falha se
|
|
32
|
+
keep-alive, absorção de sinais, `guardConsoleStream` ou isolamento forem
|
|
33
|
+
removidos do launcher.
|
|
34
|
+
|
|
9
35
|
## [0.1.08] — 2026-11-08
|
|
10
36
|
|
|
11
37
|
### Adicionado
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Gate de regressão da arquitetura do launcher (scripts/cli.mjs).
|
|
2
|
+
|
|
3
|
+
Lê o launcher e falha se qualquer proteção contra encerramento silencioso no
|
|
4
|
+
Windows (MobaXterm) for removida:
|
|
5
|
+
|
|
6
|
+
1. Keep-alive com setInterval (event loop ativo sem I/O de console);
|
|
7
|
+
2. Absorção de SIGINT/SIGHUP no Windows (apenas log, sem encerrar);
|
|
8
|
+
3. Função guardConsoleStream (anti-EPIPE com espelho em arquivo de log);
|
|
9
|
+
4. Isolamento do worker Python (detached: true / CREATE_NEW_PROCESS_GROUP).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
CAMINHO = Path(__file__).resolve().parents[2] / "scripts" / "cli.mjs"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _conteudo() -> str:
|
|
18
|
+
assert CAMINHO.exists(), f"launcher não encontrado em {CAMINHO}"
|
|
19
|
+
return CAMINHO.read_text(encoding="utf-8")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestLauncherProtections:
|
|
23
|
+
def test_keepalivo(self):
|
|
24
|
+
assert "setInterval(() => {}, 1000)" in _conteudo()
|
|
25
|
+
|
|
26
|
+
def test_absorcao_de_sinais_no_windows(self):
|
|
27
|
+
c = _conteudo()
|
|
28
|
+
assert "SIGINT" in c and "SIGTERM" in c and "SIGHUP" in c
|
|
29
|
+
assert "absorvido" in c
|
|
30
|
+
|
|
31
|
+
def test_guarda_anti_epipe(self):
|
|
32
|
+
c = _conteudo()
|
|
33
|
+
assert "guardConsoleStream" in c
|
|
34
|
+
assert "EPIPE" in c
|
|
35
|
+
assert "launcher_console.log" in c
|
|
36
|
+
|
|
37
|
+
def test_isolamento_do_worker(self):
|
|
38
|
+
c = _conteudo()
|
|
39
|
+
assert "detached: true" in c
|
|
40
|
+
assert "windowsHide: true" in c
|
|
41
|
+
|
|
42
|
+
def test_sigterm_nao_absorvido(self):
|
|
43
|
+
# SIGTERM/SIGBREAK são paradas legítimas: sempre encerram.
|
|
44
|
+
c = _conteudo()
|
|
45
|
+
assert "SIGTERM" in c and "SIGBREAK" in c
|
|
46
|
+
assert "naoAbsorver" in c
|
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { spawn, spawnSync } from 'node:child_process';
|
|
26
|
-
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs';
|
|
27
27
|
import { homedir, platform } from 'node:os';
|
|
28
28
|
import net from 'node:net';
|
|
29
29
|
import path from 'node:path';
|
|
@@ -282,6 +282,36 @@ async function procurarInstanciaAtiva(portaInicial, maxTentativas = 10) {
|
|
|
282
282
|
return null;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Guarda da consola (anti-EPIPE): se o terminal for fechado (MobaXterm, VS
|
|
287
|
+
* Code), escrever em stdout/stderr gera EPIPE — que o Node trataria como
|
|
288
|
+
* uncaughtException, matando o launcher. Esta guarda:
|
|
289
|
+
* - ignora erros com code EPIPE no evento 'error' do stream;
|
|
290
|
+
* - espelha toda a saída em storage/logs/launcher_console.log;
|
|
291
|
+
* - se a escrita original falhar, retorna true silenciosamente.
|
|
292
|
+
* NUNCA REMOVER (regra inviolável do AGENTS.md).
|
|
293
|
+
*/
|
|
294
|
+
function guardConsoleStream(stream, logFile) {
|
|
295
|
+
stream.on('error', (err) => {
|
|
296
|
+
if (err && err.code === 'EPIPE') return; // terminal fechado: ignora
|
|
297
|
+
return; // demais erros de consola nunca devem derrubar o launcher
|
|
298
|
+
});
|
|
299
|
+
const escritaOriginal = stream.write.bind(stream);
|
|
300
|
+
stream.write = (chunk, encoding, callback) => {
|
|
301
|
+
try {
|
|
302
|
+
appendFileSync(logFile, typeof chunk === 'string' ? chunk : Buffer.from(chunk));
|
|
303
|
+
} catch {
|
|
304
|
+
/* espelho de log é best-effort */
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
return escritaOriginal(chunk, encoding, callback);
|
|
308
|
+
} catch {
|
|
309
|
+
return true; // escrita silenciosa sem exceção
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
return stream;
|
|
313
|
+
}
|
|
314
|
+
|
|
285
315
|
// ---------------------------------------------------------------------------
|
|
286
316
|
// Comandos
|
|
287
317
|
// ---------------------------------------------------------------------------
|
|
@@ -416,9 +446,6 @@ function cmdStart(args) {
|
|
|
416
446
|
child.kill('SIGTERM');
|
|
417
447
|
}
|
|
418
448
|
};
|
|
419
|
-
process.on('SIGINT', () => { stop(); process.exit(0); });
|
|
420
|
-
process.on('SIGTERM', () => { stop(); process.exit(0); });
|
|
421
|
-
if (platform() === 'win32') process.on('SIGHUP', () => { stop(); process.exit(0); });
|
|
422
449
|
|
|
423
450
|
console.log(`→ Subindo servidor local em ${url} ...`);
|
|
424
451
|
const health = await waitForHealth(porta, 120000);
|
|
@@ -428,11 +455,39 @@ function cmdStart(args) {
|
|
|
428
455
|
stop();
|
|
429
456
|
process.exit(1);
|
|
430
457
|
}
|
|
458
|
+
|
|
459
|
+
// -------------------------------------------------------------------------
|
|
460
|
+
// Sinais (regra inviolável do AGENTS.md)
|
|
461
|
+
// MobaXterm envia SIGINT/SIGHUP espúrios (perda de foco, minimização): no
|
|
462
|
+
// Windows esses sinais são APENAS ABSORVIDOS (log), sem encerrar o launcher,
|
|
463
|
+
// para o app não morrer sozinho. SIGTERM e SIGBREAK são paradas legítimas e
|
|
464
|
+
// sempre encerram. No Linux/macOS, SIGINT/SIGHUP mantêm o stop gracioso.
|
|
465
|
+
// -------------------------------------------------------------------------
|
|
466
|
+
const ehWindows = platform() === 'win32';
|
|
467
|
+
const naoAbsorver = () => { stop(); process.exit(0); };
|
|
468
|
+
const absorverWindows = (nome) => () => {
|
|
469
|
+
console.log(`[LAUNCHER] Sinal absorvido no Windows (${nome}) — o LeadMaster continua rodando.`);
|
|
470
|
+
};
|
|
471
|
+
process.on('SIGINT', ehWindows ? absorverWindows('SIGINT') : naoAbsorver);
|
|
472
|
+
process.on('SIGHUP', ehWindows ? absorverWindows('SIGHUP') : naoAbsorver);
|
|
473
|
+
process.on('SIGTERM', naoAbsorver);
|
|
474
|
+
if (ehWindows) {
|
|
475
|
+
// SIGBREAK (Ctrl+Break): parada legítima também no Windows.
|
|
476
|
+
process.on('SIGBREAK', naoAbsorver);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Keep-alive: mantém o event loop ativo mesmo sem I/O de console (MobaXterm
|
|
480
|
+
// minimizado / sem stream ativo) — evita o encerramento silencioso.
|
|
481
|
+
setInterval(() => {}, 1000);
|
|
482
|
+
|
|
431
483
|
console.log(`✓ LeadMaster no ar em ${url} (dados: ${home})`);
|
|
432
484
|
console.log('');
|
|
433
485
|
console.log(`ℹ Abra ${url} no navegador quando quiser — o LeadMaster nunca abre janelas sozinho.`);
|
|
434
|
-
console.log(
|
|
435
|
-
|
|
486
|
+
console.log(
|
|
487
|
+
ehWindows
|
|
488
|
+
? 'ℹ Para encerrar: Ctrl+Break ou feche a janela (Ctrl+C é absorvido no Windows contra sinais espúrios do MobaXterm).'
|
|
489
|
+
: 'ℹ Para encerrar: Ctrl+C.',
|
|
490
|
+
);
|
|
436
491
|
})().catch((err) => {
|
|
437
492
|
console.error('✗ Erro inesperado ao iniciar:', err && err.message ? err.message : err);
|
|
438
493
|
process.exit(1);
|
|
@@ -498,6 +553,15 @@ const rawArgs = process.argv.slice(2);
|
|
|
498
553
|
const args = parseArgs(rawArgs);
|
|
499
554
|
const command = args._[0] || 'start';
|
|
500
555
|
|
|
556
|
+
// Guarda da consola (anti-EPIPE) aplicada ANTES de qualquer escrita do
|
|
557
|
+
// launcher: espelha stdout/stderr em storage/logs/launcher_console.log e
|
|
558
|
+
// ignora EPIPE (terminal fechado) — regra inviolável do AGENTS.md.
|
|
559
|
+
const homeLauncher = resolveHome(args);
|
|
560
|
+
const LOG_DIR = path.join(homeLauncher, 'storage', 'logs');
|
|
561
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
562
|
+
const CONSOLE_LOG = path.join(LOG_DIR, 'launcher_console.log');
|
|
563
|
+
[process.stdout, process.stderr].forEach((stream) => guardConsoleStream(stream, CONSOLE_LOG));
|
|
564
|
+
|
|
501
565
|
switch (command) {
|
|
502
566
|
case 'install': cmdInstall(args); break;
|
|
503
567
|
case 'start': cmdStart(args); break;
|