Commit Graph

8 Commits

Author SHA1 Message Date
egutierrez 7a94160fd2 feat: catch-up de decisiones previas (Webpage→Url, anti-bot, UI 2-col, tests cross-platform)
Bloque de cambios revisados y validados con el usuario en sesiones
previas que no habian aterrizado en commits propios. Lista por tema:

* enrichers: web_search ahora usa lite.duckduckgo.com como endpoint
  primario (mas tolerante con bot detection desde IP residencial),
  con fallback al endpoint html. Detecta pagina captcha y emite
  error claro si ambos fallan. Anyade _DDGLiteParser para el formato
  lite + auto-pick de parser por contenido.

* enrichers: tipo Webpage unificado en Url (campos de cuerpo
  cacheado viven en metadata del Url). Manifests actualizados
  (applies_to: [Url]). fetch_webpage ya no convierte Url->Webpage.

* enrichers/manifest: campo `params` parseado a EnricherSpec.params
  (name, type, default_value, description). UI puede renderizar
  dialog de configuracion.

* jobs: fix de path conversion para Python embebido nativo Windows
  (no convertir a /mnt/c/... cuando el subproceso es Windows-native;
  solo cuando es bash o python via WSL).

* main.cpp: ventana ImGui (no modal) "Run enricher" con layout
  2-col (label izq, input der). Inserta job con JSON tipado. Layout
  clustering apretado: hijos del mismo anchor en un solo anillo
  alrededor del padre, sin desperdigar por anillos crecientes.

* views: inspector con layout 2-col via BeginTable (Identity,
  Schema fields, Extras). Description full-width debajo de su label.

* tests: portable conftest (auto-detecta REGISTRY_ROOT, PYTHON_BIN,
  ENRICHERS_DIR para WSL y Windows portable). _runner.py trampoline
  inyecta stub via sys.path porque embedded Python ignora PYTHONPATH.
  Tests bash-only (vendor_script, freeze, dispatcher bash, resolver
  Linux-binary) skipean en Windows. Tests existentes adaptados a
  Webpage->Url.

Resultado actual: 32 passed WSL, 21 passed + 11 skipped Windows.
2026-05-03 14:41:28 +02:00
egutierrez 8623732d6d feat(graph_explorer): adopta layout assets/ via fn::asset_path
Junto con el cambio del framework (commit 81d8a7c9), graph_explorer
ahora resuelve enrichers/, runtime Python y gx-cli desde
<exe_dir>/assets/ con fallback a las rutas dev legacy.

- main.cpp: enrichers_dir busca primero <exe_dir>/assets/enrichers/
  (deploy con /compile). Fallback a <app_dir>/enrichers/ del repo
  cuando se ejecuta desde build/ (modo dev).
- jobs.cpp::resolve_python_runtime: incluye
  <exe_dir>/assets/runtime/python/{python.exe|bin/python3} como
  primera opcion de la cadena de fallback. La opcion legacy sin
  assets/ queda como segundo intento.
- chat.cpp: gxcli_path busca <exe_dir>/assets/gx-cli{.exe} con
  fallback a <app_dir>/gx-cli para modo dev.

Tests: 32/32 verde. Build Linux + Windows OK. Deploy fresco a
Desktop con todas las 6 apps confirma layout limpio:
  <app>.exe + (duckdb.dll si aplica) + assets/ + local_files/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:50:44 +02:00
egutierrez 30f6f3758f feat(jobs): runtime Python embebido + cadena de fallback (issue 0033 fase B)
Permite distribuir graph_explorer.exe Windows sin dependencia de WSL
ni del .venv del registry. Tambien funciona en Linux como bundle
autocontenido portable.

Cambios:

1. tools/freeze_python_runtime.sh
   - Linux: copia python-build-standalone (uv) ~87 MB,
     elimina marker EXTERNALLY-MANAGED, instala wheels.
   - Windows: descarga python-3.12.7-embed-amd64.zip oficial
     (~12 MB), habilita site-packages, instala wheels via
     pip install --target --platform win_amd64.
   - Idempotente via runtime/.lock con SHA256 del estado.
   - Lee python_runtime_deps del frontmatter de app.md.

2. jobs.cpp::cached_python_runtime() — resolver con cadena:
     1. <exe_dir>/runtime/python/{python.exe|bin/python3}  (embedded)
     2. $FN_PYTHON                                         (env)
     3. <registry_root>/python/.venv/bin/python3           (registry_venv)
     4. python3 del PATH                                   (system)
   Loggea procedencia al iniciar jobs_init.

3. POSIX run_subprocess: usa el runtime resuelto en lugar del
   path hardcodeado.

4. Windows run_subprocess: ramifica por needs_wsl. Si embedded
   o env, lanza Python Windows nativo via CreateProcessW
   directamente (run_path tambien Windows nativo). Solo el
   legacy registry_venv sigue por wsl.exe.

5. app.md: nuevos campos python_runtime: true y
   python_runtime_deps: [requests, certifi, urllib3].

6. .gitignore extendido con runtime/, projects/, _vendored/,
   .vendor.lock, binarios Go de enrichers.

Tests: 26/26 verde — 16 originales + 6 dispatcher fase A + 4
nuevos del resolver fase B (con/sin embed, FN_PYTHON, idempotencia
del freeze script).

Smoke E2E manual: runtime/python/bin/python3 ejecuta web_search
con cwd /tmp y registry_root pasado en ctx, sin tocar el .venv del
registry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:51:02 +02:00
egutierrez fce3f97d53 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>
2026-05-02 16:15:03 +02:00
egutierrez 5fe856b30e fix(jobs): resolver ops_db_path absoluto y normalizar backslashes
build_stdin_json enviaba ops_db_path tal cual al subprocess Python
(tipicamente "projects/<slug>/operations.db", relativo). Si el cwd
del proceso padre no era el dir del proyecto, sqlite3.connect
creaba un fichero vacio en otra ruta y el primer SELECT fallaba con
"no such table: entities".

Anade lambda absify que normaliza separadores (\\ -> /) antes de
std::filesystem::absolute (en Linux \\ es char literal del nombre,
no separador) y absolutiza ops_db, app_dir y registry_root antes
del to_wsl_path. Cubre los 5 enrichers de una sola vez.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:10:21 +02:00
egutierrez c3ce9956f7 feat(jobs): implementacion Win32 — wsl.exe + path translation (issue 0026)
Sustituye el stub Windows por la implementacion real:

C++:
- Bloque #ifdef _WIN32 con CreateProcessW + 3 anonymous pipes
  (CreatePipe + SetHandleInformation), STARTF_USESTDHANDLES,
  CREATE_NO_WINDOW, ReadFile/WriteFile, WaitForSingleObject con polling
  para soportar cancelacion via TerminateProcess.
- Helper to_wsl_path: convierte paths Windows a WSL antes de mandarlos
  al subprocess. Soporta:
    * "C:\\..."                   -> "/mnt/c/..."
    * "\\\\wsl.localhost\\<distro>\\..." -> "/..."
    * "\\\\wsl$\\<distro>\\..."         -> "/..."
    * "/..."                      -> tal cual
  En POSIX la funcion es no-op.
- build_stdin_json siempre normaliza ops_db_path/app_dir/cache_dir/
  registry_root a paths WSL — el run.py corre dentro de WSL y solo
  entiende paths /home, /mnt, etc.
- Subprocess invocacion: `wsl.exe --cd <root_wsl> -- <python_wsl> <run_wsl>`.
  Asume que el usuario tiene WSL instalado y la distro Ubuntu (o ajusta
  FN_REGISTRY_ROOT al UNC adecuado).
- kill_proc unificado: TerminateProcess en Win32, kill(SIGTERM) en POSIX.
- JobControl con HANDLE+pid en Win32, pid_t en POSIX.

main.cpp:
- resolve_registry_root con fallback Windows: si FN_REGISTRY_ROOT env
  no esta y getcwd no encuentra registry.db (caso del .exe en Desktop),
  usa "\\\\wsl.localhost\\Ubuntu\\home\\lucas\\fn_registry". El usuario
  cambia el UNC via env var si su distro tiene otro nombre.

Build:
- cpp/build/windows/apps/graph_explorer/graph_explorer.exe linkea limpio
  contra MinGW; solo dependencias windows.h estandar (kernel32, etc.).
- Linux smoke test sigue detectando los 4 enrichers tras la
  refactorizacion compartida.

Notas operativas para el usuario Windows:
- Ejecutar el .exe desde C:\\Users\\lucas\\Desktop\\apps\\graph_explorer\\
  (doble clic). El primer arranque tarda ~1 s mas por cold-start de wsl.exe.
- Si la distro no es Ubuntu, setear FN_REGISTRY_ROOT con el UNC correcto
  (ej. "\\\\wsl.localhost\\Debian\\home\\lucas\\fn_registry").
- Cancelar un job en Windows usa TerminateProcess (mas brutal que SIGTERM
  pero los run.py no tienen estado critico — sqlite3 rollback automatico
  por la transaccion implicita).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:49:36 +02:00
egutierrez a7c227354b fix(jobs): stub Windows para que la build cross-compile (issue 0026)
El sistema de jobs usa fork+exec+pipes POSIX que no existen en MinGW.
Anade un stub _WIN32 que devuelve false en jobs_init y no-op en el resto,
de forma que la app compila para Windows pero los enrichers quedan
desactivados ahi. La build Linux/WSL conserva la implementacion completa.

TODO futuro: implementacion Windows con CreateProcess + anonymous pipes
+ TerminateProcess. No urgente — el desarrollo principal es WSL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:42:46 +02:00
egutierrez 6df04652d8 feat(jobs): sistema de jobs asincronos + panel UI (issue 0026)
Infra para correr enrichers en background mientras la app sigue interactiva.

C++:
- jobs.{h,cpp}: tabla jobs en graph_explorer.db, JobRunner con N=2 std::thread
  workers, fork+exec POSIX con pipes, parser de PROGRESS:<float> <stage> en
  stderr, captura de stdout JSON, persistencia + dirty_counter.
- enrichers.{h,cpp}: scanner de enrichers/<id>/manifest.yaml, parser YAML
  minimo (id/name/description/applies_to), filtro por tipo de nodo.
- views_jobs.cpp: panel "Jobs" dockeable con tabla (status/enricher/target/
  progress/time), filtro all/active/done/errors, cancelar/borrar inline.

Wiring:
- main.cpp: resolve_registry_root() (FN_REGISTRY_ROOT env o subir desde cwd
  buscando registry.db), jobs_init/enrichers_load antes de fn::run_app,
  jobs_shutdown al cerrar, dirty_counter -> want_reload, jobs_set_ops_db al
  cambiar de proyecto.
- main.cpp:render_context_menu: menu "Run enricher" sustituye placeholder
  con submenu filtrado por type_ref via enrichers_for_type. Submit abre
  panel Jobs auto.
- views.h: AppState::panel_jobs flag + decl views_jobs().
- CMakeLists.txt: anade jobs.cpp + enrichers.cpp + views_jobs.cpp y enlaza
  Threads::Threads.

Wire protocol enricher (subprocess Python):
- stdin:  JSON con node_id, metadata, ops_db_path, app_dir, cache_dir,
          registry_root, params.
- stderr: PROGRESS:<float> <stage> + LOG lineas libres.
- stdout: JSON resumen al final.
- exit 0 = ok, !=0 = error con stderr capturado en panel Jobs.

El run.py escribe directamente al operations.db (sqlite3 stdlib) — C++ solo
orquesta, no parsea entities/relations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:24:37 +02:00