mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
Merge pull request #67287 from NousResearch/bb/salvage-38614-resume-cwd
fix(cli): restore session cwd on mid-chat /resume and /sessions (supersedes #38614)
This commit is contained in:
commit
d015500d45
2 changed files with 81 additions and 0 deletions
|
|
@ -834,6 +834,14 @@ class CLICommandsMixin:
|
|||
else:
|
||||
_cprint(f" ↻ Resumed session {target_id}{title_part} — no messages, starting fresh.")
|
||||
|
||||
# Retarget the process + tool cwd to where the session was started, so a
|
||||
# mid-chat /resume (and /sessions <id>, which delegates here) lands in the
|
||||
# same directory as a startup `hermes -c`/`--resume`. The startup resume
|
||||
# paths already call this; without it, the terminal/code-exec tools and
|
||||
# relative-path resolution keep operating in the wrong repo. Idempotent
|
||||
# and a no-op when the session recorded no cwd. See #38562.
|
||||
self._restore_session_cwd(session_meta)
|
||||
|
||||
def _handle_sessions_command(self, cmd_original: str) -> None:
|
||||
"""Handle /sessions [list|<id_or_title>] — browse or resume previous sessions.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import os
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -153,6 +154,78 @@ class TestCliResumeCommand:
|
|||
assert "<half" in printed
|
||||
|
||||
|
||||
class TestCliResumeRestoresCwd:
|
||||
"""Mid-chat /resume must retarget the working directory to where the
|
||||
session was started — the same contract as a startup ``hermes -c`` /
|
||||
``--resume``.
|
||||
|
||||
Regression coverage for #38562: ``_restore_session_cwd()`` was wired into
|
||||
the startup resume paths but not into ``_handle_resume_command()``, so an
|
||||
interactive ``/resume`` (and ``/sessions <id>``, which delegates here) left
|
||||
the process + ``TERMINAL_CWD`` pointing at whatever directory the user had
|
||||
cd'd into — so the terminal/code-exec tools and relative paths ran in the
|
||||
wrong repo.
|
||||
"""
|
||||
|
||||
def _resumable_cli(self, session_meta):
|
||||
cli_obj = _make_cli()
|
||||
cli_obj._session_db.get_session.return_value = session_meta
|
||||
cli_obj._session_db.get_messages_as_conversation.return_value = [
|
||||
{"role": "user", "content": "hello"},
|
||||
]
|
||||
cli_obj._session_db.resolve_resume_session_id.return_value = session_meta["id"]
|
||||
return cli_obj
|
||||
|
||||
def test_handle_resume_restores_recorded_cwd(self, tmp_path):
|
||||
recorded = str(tmp_path)
|
||||
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir", "cwd": recorded})
|
||||
|
||||
with (
|
||||
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
|
||||
patch("cli._cprint"),
|
||||
patch.object(cli_obj, "_console_print"),
|
||||
patch("os.chdir") as mock_chdir,
|
||||
patch.dict(os.environ, {}, clear=False),
|
||||
):
|
||||
cli_obj._handle_resume_command("/resume Dir")
|
||||
# Assert inside the patch.dict scope — it restores os.environ on exit.
|
||||
assert os.environ.get("TERMINAL_CWD") == recorded
|
||||
|
||||
mock_chdir.assert_called_once_with(recorded)
|
||||
|
||||
def test_handle_resume_without_recorded_cwd_does_not_chdir(self):
|
||||
# Gateway/remote/older sessions record no cwd — restore must no-op.
|
||||
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir"})
|
||||
|
||||
with (
|
||||
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
|
||||
patch("cli._cprint"),
|
||||
patch.object(cli_obj, "_console_print"),
|
||||
patch("os.chdir") as mock_chdir,
|
||||
):
|
||||
cli_obj._handle_resume_command("/resume Dir")
|
||||
|
||||
mock_chdir.assert_not_called()
|
||||
|
||||
def test_sessions_command_restores_recorded_cwd(self, tmp_path):
|
||||
# /sessions <id> delegates to the resume flow, so it restores cwd too.
|
||||
recorded = str(tmp_path)
|
||||
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir", "cwd": recorded})
|
||||
|
||||
with (
|
||||
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
|
||||
patch("cli._cprint"),
|
||||
patch.object(cli_obj, "_console_print"),
|
||||
patch("os.chdir") as mock_chdir,
|
||||
patch.dict(os.environ, {}, clear=False),
|
||||
):
|
||||
cli_obj._handle_sessions_command("/sessions Dir")
|
||||
# Assert inside the patch.dict scope — it restores os.environ on exit.
|
||||
assert os.environ.get("TERMINAL_CWD") == recorded
|
||||
|
||||
mock_chdir.assert_called_once_with(recorded)
|
||||
|
||||
|
||||
class TestPendingResumeNumberedSelection:
|
||||
"""Bare `/resume` arms a one-shot prompt so the next bare number resumes.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue