feat(vault): zero-setup UX — save a login on the page that needs it, managers auto-detected, one "Passwords & Logins" surface

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.
This commit is contained in:
Teknium
2026-09-10 05:15:25 -07:00
parent f5e28f8119
commit 98d11c95f4
31 changed files with 806 additions and 369 deletions
+5 -3
View File
@@ -3000,8 +3000,9 @@ class HermesCLI(CLIProcessNotificationsMixin, CLIAgentSetupMixin, CLICommandsMix
set_sudo_password_callback(self._sudo_password_callback)
set_approval_callback(self._approval_callback)
set_secret_capture_callback(self._secret_capture_callback)
from agent.vault_backends.unlock import set_unlock_prompt_callback
from agent.vault_backends.unlock import set_save_login_prompt_callback, set_unlock_prompt_callback
set_unlock_prompt_callback(self._vault_unlock_callback)
set_save_login_prompt_callback(self._vault_save_login_callback)
try:
from tools.computer_use_tool import set_approval_callback as _set_cu_cb
@@ -3942,9 +3943,10 @@ class HermesCLI(CLIProcessNotificationsMixin, CLIAgentSetupMixin, CLICommandsMix
with suppress(Exception):
from tools.voice_mode import cleanup_temp_recordings
cleanup_temp_recordings()
from agent.vault_backends.unlock import lock as _vault_lock, set_unlock_prompt_callback
from agent.vault_backends.unlock import (lock as _vault_lock, set_save_login_prompt_callback,
set_unlock_prompt_callback)
for _unset in (set_sudo_password_callback, set_approval_callback, set_secret_capture_callback,
set_unlock_prompt_callback):
set_unlock_prompt_callback, set_save_login_prompt_callback):
_unset(None)
_vault_lock() # session tokens for external password managers die with the session
# On SIGHUP/SIGTERM the agent thread may be reaped before its own persistence runs.