Pressing Refresh Agents (or Test Connection — both trigger fetch + table
re-render) crashed the app with Windows exit code 5 (access violation).
Root cause: agents_tbl_state was default-constructed, so
State.col_visible (std::vector<bool>) and State.col_order
(std::vector<int>) were empty. render_grid_stage0 indexes them by column
index up to N_COLS=11 without bounds checking → undefined behaviour →
segfault on the first render after agents data populated.
Fix: at first render of the agents panel, assign col_visible=true * N_COLS,
fill col_order with [0..N_COLS), and ensure stages.size() >= 1. Same
pattern tql_apply.cpp uses (col_visible.assign(eff_cols, true)).
Diagnostic infra added (kept in place — minimal overhead):
- FN_DBG macro: fprintf(stderr, ...) + fflush. Survives crashes that
fn_log's buffered file output doesn't.
- --auto-refresh CLI flag: triggers fetch_agents_async at frame 30,
auto-exits at frame 180 (~3s @ 60Hz). Headless smoke for CI.
- DBG breadcrumbs through main → load_apikey → fn::run_app → render →
fetch_agents_async (thread enter/request/response/parse/exit) → render
table (pre/post). Each step flushes stderr immediately.
E2E regression guard: test_app_survives_auto_refresh_cycle. Runs the .exe
with --auto-refresh, asserts exit 0, asserts the breadcrumb chain reaches
both "fetch thread parsed" and "agents_panel POST-render" in stderr. 25
tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Launching from the App Hub (or any double-click) no longer needs
AGENTS_API_KEY manually injected. Order of resolution:
1. AGENTS_API_KEY env var → apikey_source = "env"
2. `pass agentes/api-key` shell → apikey_source = "pass"
3. neither → apikey_source = "missing"
On Windows the fallback shells via `wsl.exe -e sh -c "pass ... | head -n1"`
so the secret stays in the WSL user's GnuPG keychain (never copied to a
Windows file). On Linux it's a direct popen of `pass ...`.
Failure mode: GPG agent locked → empty output → "missing" state in UI
with a "Retry pass" button (user runs `pass agentes/api-key` once to
unlock the agent, clicks Retry, app refetches without restart).
Connection panel shows the active source:
✓ loaded from AGENTS_API_KEY env var
✓ loaded via `pass agentes/api-key`
⚠ apikey not found (env empty + pass failed)
--connect-test uses the same two-tier resolution so e2e exercises the
production code path.
E2E: renamed test_connect_fails_without_apikey →
test_connect_falls_back_to_pass_when_env_empty. Verifies that with
empty env, the .exe still returns OK N. Skips if `pass` is locked.
All 24 tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Security: apikey NUNCA mas vive en local DB ni se introduce por UI.
Se lee al arranque via getenv("AGENTS_API_KEY"), sourced typically con
`pass agentes/api-key` antes de lanzar el .exe.
Connection panel:
- Removed apikey TextInput (+show/hide button)
- Removed UI mask flag
- Replaced con indicador "loaded from env" verde / "missing" rojo
- Hint visible: "Launch with: AGENTS_API_KEY=$(pass agentes/api-key) <exe>"
Persistencia local:
- db_save_connection: solo base_url (blob de apikey ya no se cifra)
- db_load_connection: solo base_url
- No mas roundtrip a fn_secret en runtime de la UI (la funcion del
registry secret_store_cpp_infra sigue util para otras apps)
CLI:
- --connect-test <url> ahora lee apikey de AGENTS_API_KEY env var
- trim_url() en make_url + en CLI defensivo contra paste con CR/LF
- run_self_test sin cambios (secret_store roundtrip se mantiene)
E2E tests (tests/test_connect_e2e.py, 5 casos):
- test_connect_succeeds_with_valid_apikey
- test_connect_fails_without_apikey
- test_connect_fails_on_bad_host
- test_count_matches_direct_curl
- test_url_trim_robust_to_whitespace
Lanzar con:
AGENTS_API_KEY=$(pass agentes/api-key) \
python3 -m pytest -v tests/test_connect_e2e.py
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>