diff --git a/plugins/memory/hindsight/__init__.py b/plugins/memory/hindsight/__init__.py index bc82bc40fb5d..277590add9f1 100644 --- a/plugins/memory/hindsight/__init__.py +++ b/plugins/memory/hindsight/__init__.py @@ -478,7 +478,9 @@ def save_config(self, values, hermes_home): existing = {} if config_path.exists(): try: - existing = json.loads(config_path.read_text()) + parsed = json.loads(config_path.read_text()) + if isinstance(parsed, dict): + existing = parsed except Exception: pass existing.update(values) @@ -589,13 +591,29 @@ def post_setup(self, hermes_home: str, config: dict) -> None: val = input(f" LLM model [{default_model}]: ").strip() provider_config["llm_model"] = val or default_model - sys.stdout.write(" LLM API key: ") + effective_config = dict(provider_config) + config_path = Path(hermes_home) / "hindsight" / "config.json" + try: + saved_config = json.loads(config_path.read_text(encoding="utf-8")) + if isinstance(saved_config, dict): + effective_config.update(saved_config) + except Exception: + pass + effective_config.update(provider_config) + existing_llm_key = _load_simple_env(Path(hermes_home) / ".env").get("HINDSIGHT_LLM_API_KEY", "") + if not existing_llm_key: + existing_llm_key = _load_simple_env(_embedded_profile_env_path(effective_config)).get( + "HINDSIGHT_API_LLM_API_KEY", + "", + ) + prompt = " LLM API key (blank to keep existing): " if existing_llm_key else " LLM API key: " + sys.stdout.write(prompt) sys.stdout.flush() llm_key = getpass.getpass(prompt="") if sys.stdin.isatty() else sys.stdin.readline().strip() # Always write explicitly (including empty) so the provider sees "" # rather than a missing variable. The daemon reads from .env at # startup and fails when HINDSIGHT_LLM_API_KEY is unset. - env_writes["HINDSIGHT_LLM_API_KEY"] = llm_key + env_writes["HINDSIGHT_LLM_API_KEY"] = llm_key or existing_llm_key # Step 4: Save everything provider_config["bank_id"] = "hermes" diff --git a/tests/plugins/memory/test_hindsight_provider.py b/tests/plugins/memory/test_hindsight_provider.py index 5f1290b2f16a..17fc4217fac6 100644 --- a/tests/plugins/memory/test_hindsight_provider.py +++ b/tests/plugins/memory/test_hindsight_provider.py @@ -329,6 +329,66 @@ def test_local_embedded_setup_preserves_existing_key_when_input_left_blank(self, profile_env = user_home / ".hindsight" / "profiles" / "hermes.env" assert profile_env.exists() + assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n" + assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text() + + def test_local_embedded_setup_preserves_existing_key_from_nondefault_profile_env_when_input_left_blank(self, tmp_path, monkeypatch): + hermes_home = tmp_path / "hermes-home" + user_home = tmp_path / "user-home" + user_home.mkdir() + monkeypatch.setenv("HOME", str(user_home)) + + selections = iter([1, 0]) # local_embedded, openai + monkeypatch.setattr("hermes_cli.memory_setup._curses_select", lambda *args, **kwargs: next(selections)) + monkeypatch.setattr("shutil.which", lambda name: None) + monkeypatch.setattr("builtins.input", lambda prompt="": "") + monkeypatch.setattr("sys.stdin.isatty", lambda: True) + monkeypatch.setattr("getpass.getpass", lambda prompt="": "") + monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None) + + provider = HindsightMemoryProvider() + provider.save_config({"profile": "coder"}, str(hermes_home)) + + profile_env = user_home / ".hindsight" / "profiles" / "coder.env" + profile_env.parent.mkdir(parents=True, exist_ok=True) + profile_env.write_text("HINDSIGHT_API_LLM_API_KEY=existing-key\n") + + provider.post_setup(str(hermes_home), {"memory": {}}) + + hermes_env = user_home / ".hindsight" / "profiles" / "hermes.env" + assert profile_env.exists() + assert not hermes_env.exists() + assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n" + assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text() + + def test_local_embedded_setup_ignores_nondict_saved_config_when_input_left_blank(self, tmp_path, monkeypatch): + hermes_home = tmp_path / "hermes-home" + user_home = tmp_path / "user-home" + user_home.mkdir() + monkeypatch.setenv("HOME", str(user_home)) + + selections = iter([1, 0]) # local_embedded, openai + monkeypatch.setattr("hermes_cli.memory_setup._curses_select", lambda *args, **kwargs: next(selections)) + monkeypatch.setattr("shutil.which", lambda name: None) + monkeypatch.setattr("builtins.input", lambda prompt="": "") + monkeypatch.setattr("sys.stdin.isatty", lambda: True) + monkeypatch.setattr("getpass.getpass", lambda prompt="": "") + monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None) + + config_path = hermes_home / "hindsight" / "config.json" + config_path.parent.mkdir(parents=True, exist_ok=True) + config_path.write_text("[]") + + env_path = hermes_home / ".env" + env_path.parent.mkdir(parents=True, exist_ok=True) + env_path.write_text("HINDSIGHT_LLM_API_KEY=existing-key\n") + + provider = HindsightMemoryProvider() + provider.post_setup(str(hermes_home), {"memory": {}}) + + profile_env = user_home / ".hindsight" / "profiles" / "hermes.env" + assert profile_env.exists() + assert (hermes_home / ".env").read_text() == "HINDSIGHT_LLM_API_KEY=existing-key\nHINDSIGHT_TIMEOUT=120\n" assert "HINDSIGHT_API_LLM_API_KEY=existing-key\n" in profile_env.read_text()