Nobody should have to learn `hermes vault add` or find a toggle before "log into GitHub" works.
- browser_vault_save_login: when the agent reaches a sign-in page with no saved login it asks the user
on THEIR surface (CLI two-step panel on the sudo modal: identifier shown, password masked; Desktop
card with labelled Email/username + Password fields). The answer goes to the encrypted vault bound to
the page origin and is filled at once; the model gets back only the handle and identifier. Declining
returns save_declined; headless sessions get prompt_unavailable. Never a password in chat.
- Vault tools ride with the browser toolset (check_browser_requirements) instead of appearing only once
the vault has items — an empty vault is exactly when save_login is needed. browser_vault_list hints
at it when empty.
- 1Password / Bitwarden are login sources as soon as their CLI is installed; `vault.<name>.enabled`
is opt-OUT only. Settings shows Detected/Locked/Unlocked/Off/Not detected with a switch only for
installed managers; `hermes vault sources` reports detection, `--disable`/`--enable` flip the opt-out.
- Desktop nav/page renamed "Passwords & Logins"; empty state tells the user they do not need to add
anything; all five locales updated. Docs rewritten from "how it works" to "say log into X".
- New per-thread SaveLoginPrompt callback (agent/vault_backends/unlock.py) installed beside the unlock
prompt on every CLI site and the gateway bridge (vault.save_login.request/respond/expire), propagated
to worker threads via tools.thread_context.
Live: CLI PTY (real model, packaged Chromium, local login server) — panel shown, identifier + masked
password typed, server received the correct password, password absent from terminal transcript and
from every file under HERMES_HOME outside vault/. Native Electron (headless, isolated HOME/HERMES_HOME,
own Vite + CDP port) — card shown, "Save & sign in", server received the password, Settings lists the
saved item, password absent from the rendered UI.
Live Desktop repro: the fixture model called browser_vault_unlock and got
unlock_unavailable although the renderer was interactive. tool_executor runs
handlers on a propagated worker thread; thread_context only copied the
approval and sudo thread-local callbacks, so the unlock prompt registered by
_wire_callbacks was invisible there and can_prompt_here() said nobody could
answer. The callback table in thread_context now lists every per-thread
prompt (approval, sudo, vault unlock) so a new one cannot silently drop off
worker threads again. After the fix the same turn shows the masked card and
completes.
vault.sources / `hermes vault sources` report a manager as installed when
its configured binary_path exists, not only when it is on PATH.
Worker threads that dispatch Hermes tools started with an empty contextvars.Context and no thread-local approval/sudo callbacks. Add tools/thread_context.propagate_context_to_thread factoring that capture/install/clear lifecycle (mirrors the GHSA-qg5c-hvr5-hjgr pattern), and refactor agent/tool_executor onto it so the security-critical logic lives in one audited place. Update the contextvar-propagation source guard for the new call shape.
Refs #33057