Compare commits

...

3 Commits

Author SHA1 Message Date
alt-glitch
2246c81d0d chore: add poruru-code to AUTHOR_MAP 2026-04-25 10:05:44 +05:30
alt-glitch
8877688b34 fix(hindsight): preserve custom timeout on reconfig
post_setup() used self._config to read the existing timeout, but
self._config is None during setup. Read from .env instead.
2026-04-25 09:55:13 +05:30
poruru-code
9d42aca29a fix(hindsight): preserve existing LLM key on blank local_embedded setup
Salvaged from PR #15309 (poruru-code) + PR #15233 (LeonSGP43).
Cherry-picked key preservation logic and config hardening from #15309,
combined with masked-key prompt UX from #15233.
2026-04-25 09:53:25 +05:30
3 changed files with 116 additions and 6 deletions

View File

@@ -478,7 +478,9 @@ class HindsightMemoryProvider(MemoryProvider):
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,12 +591,35 @@ class HindsightMemoryProvider(MemoryProvider):
val = input(f" LLM model [{default_model}]: ").strip()
provider_config["llm_model"] = val or default_model
sys.stdout.write(" LLM API key: ")
existing_llm_key = os.environ.get("HINDSIGHT_LLM_API_KEY", "")
if not existing_llm_key:
existing_llm_key = _load_simple_env(Path(hermes_home) / ".env").get(
"HINDSIGHT_LLM_API_KEY",
"",
)
if not existing_llm_key:
saved_config = dict(provider_config)
config_path = Path(hermes_home) / "hindsight" / "config.json"
try:
parsed = json.loads(config_path.read_text(encoding="utf-8"))
if isinstance(parsed, dict):
saved_config.update(parsed)
except Exception:
pass
saved_config.update(provider_config)
existing_llm_key = _load_simple_env(
_embedded_profile_env_path(saved_config)
).get("HINDSIGHT_API_LLM_API_KEY", "")
if existing_llm_key:
masked = f"...{existing_llm_key[-4:]}" if len(existing_llm_key) > 4 else "set"
sys.stdout.write(f" LLM API key (current: {masked}, blank to keep): ")
else:
sys.stdout.write(" LLM API key: ")
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.
if not llm_key and existing_llm_key:
llm_key = existing_llm_key
env_writes["HINDSIGHT_LLM_API_KEY"] = llm_key
# Step 4: Save everything
@@ -602,7 +627,9 @@ class HindsightMemoryProvider(MemoryProvider):
provider_config["recall_budget"] = "mid"
# Read existing timeout from config if present, otherwise use default
existing_timeout = self._config.get("timeout") if self._config else None
timeout_val = existing_timeout if existing_timeout else _DEFAULT_TIMEOUT
if not existing_timeout:
existing_timeout = _load_simple_env(Path(hermes_home) / ".env").get("HINDSIGHT_TIMEOUT")
timeout_val = int(existing_timeout) if existing_timeout else _DEFAULT_TIMEOUT
provider_config["timeout"] = timeout_val
env_writes["HINDSIGHT_TIMEOUT"] = str(timeout_val)
config["memory"]["provider"] = "hindsight"

View File

@@ -504,6 +504,8 @@ AUTHOR_MAP = {
"screenmachine@gmail.com": "teknium1",
"chenzeshi@live.com": "chen1749144759",
"mor.aleksandr@yahoo.com": "MorAlekss",
"poruru.code@gmail.com": "poruru-code",
"138243371+poruru-code@users.noreply.github.com": "poruru-code",
}

View File

@@ -329,8 +329,89 @@ class TestPostSetup:
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": {}})
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()
def test_local_embedded_setup_preserves_existing_timeout(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="": "sk-test")
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
env_path = hermes_home / ".env"
env_path.parent.mkdir(parents=True, exist_ok=True)
env_path.write_text("HINDSIGHT_LLM_API_KEY=sk-test\nHINDSIGHT_TIMEOUT=300\n")
provider = HindsightMemoryProvider()
provider.post_setup(str(hermes_home), {"memory": {}})
env_content = (hermes_home / ".env").read_text()
assert "HINDSIGHT_TIMEOUT=300" in env_content
# ---------------------------------------------------------------------------
# Tool handler tests