feat(enrichers): dispatcher multi-lang go|python|bash (issue 0033 fase A)
Extiende el sistema de enrichers para soportar varios lenguajes en el
mismo registro. El manifest gana dos campos opcionales:
lang: python|go|bash (default: python — retrocompat con los 5
enrichers existentes que no lo declaran)
exec: run (basename del script o binario; default "run")
EnricherSpec ahora lleva `lang`, `exec_basename`, `disabled` y
`disabled_reason`. parse_manifest lee los nuevos campos y aplica
defaults; resolve_run_path busca <dir>/<exec>{.py|.sh|.exe|<vacio>}
segun lang + plataforma. Si el ejecutable no existe (binario Go sin
compilar, script ausente), el spec queda en el registro pero
disabled — enrichers_for_type lo oculta del menu y jobs.cpp aborta
con mensaje claro si llega un job para uno disabled.
run_subprocess (POSIX y Windows) ramifica argv segun lang:
- go -> execv del binario directamente, sin python ni wsl.exe
- bash -> /bin/bash <run_path> (en Windows: wsl.exe -- bash ...)
- python -> python3 <run_path> (default)
El call site en jobs.cpp resuelve run_path y lang via
ge::enricher_by_id() en lugar del hardcode "run.py". Los 5 enrichers
existentes siguen funcionando sin cambios — heredan lang: python por
default.
Tests pytest (22/22 verde):
- 16 regresion: los 5 enrichers actuales siguen pasando.
- 6 nuevos en test_dispatcher_lang.py: parser default a python,
parser lee lang: bash, wire protocol identico para python y
bash, enricher Go sin binario queda disabled, enricher real
sigue funcionando tras el cambio.
NO incluye: runtime Python embebido (fase B) ni badges de lang en
la UI (fase C). El issue 0033 sigue abierto hasta cerrar las dos
fases restantes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "jobs.h"
|
||||
#include "enrichers.h"
|
||||
|
||||
#include "../../../../cpp/vendor/sqlite3/sqlite3.h"
|
||||
|
||||
@@ -400,6 +401,7 @@ std::wstring utf8_to_wide(const std::string& s) {
|
||||
|
||||
ProcResult run_subprocess(const std::string& job_id,
|
||||
const std::string& run_path,
|
||||
const std::string& lang,
|
||||
const std::string& stdin_payload,
|
||||
std::shared_ptr<JobControl> ctrl)
|
||||
{
|
||||
@@ -432,20 +434,33 @@ ProcResult run_subprocess(const std::string& job_id,
|
||||
SetHandleInformation(out_r, HANDLE_FLAG_INHERIT, 0);
|
||||
SetHandleInformation(err_r, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
// Convertir paths a WSL.
|
||||
std::string run_wsl = to_wsl_path(run_path);
|
||||
std::string root_wsl = to_wsl_path(g_state->registry_root);
|
||||
std::string py_wsl = root_wsl + "/python/.venv/bin/python3";
|
||||
|
||||
// wsl.exe --cd <root> -- <python> <run.py>
|
||||
// Los argumentos van separados; wsl.exe interpreta bien rutas con espacios
|
||||
// si se quotean. En nuestro caso no esperamos espacios.
|
||||
std::wstring cmdline = L"wsl.exe --cd ";
|
||||
cmdline += utf8_to_wide(root_wsl);
|
||||
cmdline += L" -- ";
|
||||
cmdline += utf8_to_wide(py_wsl);
|
||||
cmdline += L" ";
|
||||
cmdline += utf8_to_wide(run_wsl);
|
||||
// Construir cmdline segun lang (issue 0033).
|
||||
// - "go": ejecutar el .exe nativo directamente, sin wsl.exe.
|
||||
// - "python": wsl.exe --cd <root> -- python3 <run.py> (legacy)
|
||||
// - "bash": wsl.exe --cd <root> -- bash <run.sh>
|
||||
std::wstring cmdline;
|
||||
if (lang == "go") {
|
||||
// run_path es el .exe Windows nativo. CreateProcessW lo lanza
|
||||
// tal cual. No traducimos a WSL — corre fuera de WSL.
|
||||
cmdline = L"\"";
|
||||
cmdline += utf8_to_wide(run_path);
|
||||
cmdline += L"\"";
|
||||
} else {
|
||||
std::string run_wsl = to_wsl_path(run_path);
|
||||
std::string root_wsl = to_wsl_path(g_state->registry_root);
|
||||
std::string interp;
|
||||
if (lang == "bash") {
|
||||
interp = "/bin/bash";
|
||||
} else {
|
||||
interp = root_wsl + "/python/.venv/bin/python3";
|
||||
}
|
||||
cmdline = L"wsl.exe --cd ";
|
||||
cmdline += utf8_to_wide(root_wsl);
|
||||
cmdline += L" -- ";
|
||||
cmdline += utf8_to_wide(interp);
|
||||
cmdline += L" ";
|
||||
cmdline += utf8_to_wide(run_wsl);
|
||||
}
|
||||
|
||||
std::vector<wchar_t> cmdbuf(cmdline.begin(), cmdline.end());
|
||||
cmdbuf.push_back(0);
|
||||
@@ -585,6 +600,7 @@ void kill_proc(JobControl& c) {
|
||||
|
||||
ProcResult run_subprocess(const std::string& job_id,
|
||||
const std::string& run_path,
|
||||
const std::string& lang,
|
||||
const std::string& stdin_payload,
|
||||
std::shared_ptr<JobControl> ctrl)
|
||||
{
|
||||
@@ -615,6 +631,24 @@ ProcResult run_subprocess(const std::string& job_id,
|
||||
close(p_out[0]); close(p_out[1]);
|
||||
close(p_err[0]); close(p_err[1]);
|
||||
|
||||
// Bifurcacion por lang (issue 0033).
|
||||
// - "go": execv directo del binario.
|
||||
// - "bash": /bin/bash <run_path>.
|
||||
// - "python": <registry_root>/python/.venv/bin/python3 <run_path>.
|
||||
if (lang == "go") {
|
||||
const char* argv[] = { run_path.c_str(), nullptr };
|
||||
execv(run_path.c_str(), (char* const*)argv);
|
||||
std::fprintf(stderr, "execv failed: %s\n", run_path.c_str());
|
||||
_exit(127);
|
||||
}
|
||||
if (lang == "bash") {
|
||||
const char* sh = "/bin/bash";
|
||||
const char* argv[] = { sh, run_path.c_str(), nullptr };
|
||||
execv(sh, (char* const*)argv);
|
||||
std::fprintf(stderr, "execv bash failed\n");
|
||||
_exit(127);
|
||||
}
|
||||
// Default: python.
|
||||
std::string py = g_state->registry_root + "/python/.venv/bin/python3";
|
||||
const char* argv[] = { py.c_str(), run_path.c_str(), nullptr };
|
||||
execv(py.c_str(), (char* const*)argv);
|
||||
@@ -825,8 +859,21 @@ void worker_loop() {
|
||||
if (!load_job(job_id, &ctx)) continue;
|
||||
if (ctx.status == "cancelled") continue;
|
||||
|
||||
std::string run_path = g_state->enrichers_dir + "/" + ctx.enricher_id +
|
||||
"/run.py";
|
||||
// Resolver run_path y lang desde el registro de enrichers
|
||||
// (issue 0033 — antes hardcodeaba run.py).
|
||||
const ge::EnricherSpec* spec = ge::enricher_by_id(ctx.enricher_id.c_str());
|
||||
if (!spec) {
|
||||
persist_status(job_id, "failure", "",
|
||||
"enricher no encontrado en el registro", false);
|
||||
continue;
|
||||
}
|
||||
if (spec->disabled) {
|
||||
std::string err = "enricher deshabilitado: " + spec->disabled_reason;
|
||||
persist_status(job_id, "failure", "", err, false);
|
||||
continue;
|
||||
}
|
||||
std::string run_path = spec->run_path;
|
||||
std::string lang = spec->lang;
|
||||
|
||||
persist_status(job_id, "running", "", "", false);
|
||||
|
||||
@@ -845,7 +892,8 @@ void worker_loop() {
|
||||
ctx.id, ctx.enricher_id, ctx.node_id, ctx.params_json,
|
||||
ops_db, g_state->app_dir, g_state->registry_root);
|
||||
|
||||
ProcResult res = run_subprocess(job_id, run_path, stdin_payload, ctrl);
|
||||
ProcResult res = run_subprocess(job_id, run_path, lang,
|
||||
stdin_payload, ctrl);
|
||||
|
||||
std::string final_status, error;
|
||||
std::string result_json = res.stdout_buf;
|
||||
|
||||
Reference in New Issue
Block a user