fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
113 lines
4.0 KiB
Markdown
113 lines
4.0 KiB
Markdown
---
|
|
id: "0071f"
|
|
title: "Extraer `subprocess_streamer` a cpp/functions/core/ (sub-issue de 0071)"
|
|
status: pendiente
|
|
type: feature
|
|
domain:
|
|
- registry-quality
|
|
scope: registry-only
|
|
priority: media
|
|
depends: []
|
|
blocks: []
|
|
related: []
|
|
created: 2026-05-10
|
|
updated: 2026-05-17
|
|
tags: []
|
|
---
|
|
|
|
## Contexto
|
|
|
|
Sub-issue derivado de 0071 tras auditar paneles C++. La regla "rule of three" se cumple HOY para una primitiva Tier 4: spawn de subprocesos con captura de stdout/stderr en stream, sin wrapper compartido.
|
|
|
|
Tres reimplementaciones del mismo patron en `projects/osint_graph/apps/graph_explorer/`:
|
|
|
|
| Sitio | Linea | Mecanismo |
|
|
|---|---|---|
|
|
| `chat.cpp` | 295 | `popen(...)` + `fgets` loop |
|
|
| `chat.cpp` | 412 | `execvp(...)` con pipes manuales |
|
|
| `jobs.cpp` | 817 | `execvp(...)` con pipes + read-thread |
|
|
| `extract_panel.cpp` | 507 | `execvp(...)` para Python enricher |
|
|
|
|
Cada una hace: setup pipes, fork/exec, read loop, parse JSON line-by-line, push a UI queue. Codigo duplicado ~60-80 LoC en cada sitio.
|
|
|
|
## Objetivo
|
|
|
|
Funcion `subprocess_streamer` en `cpp/functions/core/` que encapsule:
|
|
- Spawn (POSIX `fork+execvp`, Windows `CreateProcess`)
|
|
- Pipes para stdin/stdout/stderr
|
|
- Read-thread con callback por linea
|
|
- Cancel/kill
|
|
- Wait + exit code
|
|
|
|
## API propuesta
|
|
|
|
```cpp
|
|
namespace fn_core {
|
|
|
|
struct SubprocessConfig {
|
|
std::vector<std::string> argv; // [exe, arg1, arg2, ...]
|
|
std::vector<std::string> env; // ["KEY=VAL", ...]; vacio = heredar
|
|
std::string cwd; // vacio = heredar
|
|
bool merge_stderr_to_stdout = false;
|
|
std::function<void(const std::string&)> on_stdout_line;
|
|
std::function<void(const std::string&)> on_stderr_line;
|
|
std::function<void(int exit_code)> on_exit;
|
|
};
|
|
|
|
struct SubprocessHandle; // opaco
|
|
|
|
// Lanza subproceso. on_* se llaman desde un read-thread interno.
|
|
SubprocessHandle* subprocess_spawn(const SubprocessConfig& cfg);
|
|
|
|
// Escribe en stdin. Thread-safe. Devuelve bytes escritos o -1.
|
|
int subprocess_write_stdin(SubprocessHandle* h, const char* data, size_t n);
|
|
|
|
// Cierra stdin (EOF al child).
|
|
void subprocess_close_stdin(SubprocessHandle* h);
|
|
|
|
// Mata el proceso (SIGTERM en POSIX, TerminateProcess en Win).
|
|
void subprocess_kill(SubprocessHandle* h);
|
|
|
|
// Espera fin. Devuelve exit_code. Si ya termino, retorna inmediato.
|
|
int subprocess_wait(SubprocessHandle* h);
|
|
|
|
// Libera recursos. Si el proceso sigue vivo, lo mata primero.
|
|
void subprocess_destroy(SubprocessHandle* h);
|
|
|
|
} // namespace fn_core
|
|
```
|
|
|
|
## Tests
|
|
|
|
- Spawn `echo hello` → on_stdout_line recibe "hello", exit 0.
|
|
- Spawn `cat` con stdin "abc\n" → on_stdout_line recibe "abc", exit 0 tras close_stdin.
|
|
- Spawn `false` → exit 1.
|
|
- Spawn comando inexistente → handle nullable o exit code distintivo.
|
|
- Kill proceso vivo → wait retorna codigo de señal.
|
|
|
|
`cpp/apps/primitives_gallery/` añade demo "Subprocess Streamer" con boton "spawn echo hello" + lista de lineas recibidas.
|
|
|
|
## Migracion de consumidores (orden)
|
|
|
|
1. **Crear** `cpp/functions/core/subprocess_streamer.{h,cpp}` + `.md` + tests.
|
|
2. **Migrar `chat.cpp`** primero (mas critico, define el flujo de error). Si funciona en chat → patron validado.
|
|
3. **Migrar `jobs.cpp`**.
|
|
4. **Migrar `extract_panel.cpp`**.
|
|
|
|
Cada migracion en su propio commit, validando por inspeccion manual que el panel sigue funcionando.
|
|
|
|
## Definicion de hecho
|
|
|
|
- `subprocess_streamer.{h,cpp,md}` registrado en `registry.db` (`fn index`).
|
|
- Tests pasan (al menos los 5 listados).
|
|
- 3 consumidores migrados (chat, jobs, extract_panel).
|
|
- `app.md` de graph_explorer actualizado: `uses_functions` añade `subprocess_streamer_cpp_core`.
|
|
- LoC eliminadas en graph_explorer: ~150-200 (3 reimplementaciones colapsadas a 1 import).
|
|
|
|
## Anti-patrones a evitar
|
|
|
|
- Async/promesa con `std::future` — over-engineering. Callbacks bastan.
|
|
- Buffering "smart" line-aware sobre bytes raw — usar `std::getline` simple.
|
|
- Soporte ptys (terminal real) — fuera de scope. Si una app necesita pty, abre issue propio.
|
|
- Prematura abstraccion de "shell vs exec". Solo `argv` directo (execvp). Si alguien quiere shell pipes, lo hace en `bash -c "..."`.
|