feat(cli): resume relaunches in the directory the session was started from (#38562)

hermes -c / --resume now reopen a session in its original working
directory. The sessions table already had a cwd column; the classic CLI
just never wrote or read it.

- run_agent._ensure_db_session stamps cwd for local CLI sessions only
  (new _launch_cwd_for_session gates out gateway/cron and non-local
  terminal backends, where a host cwd is meaningless to restore).
- cli._restore_session_cwd chdir's the process AND retargets TERMINAL_CWD
  so the terminal tool, code-exec tool, and relative-path resolution all
  land in the restored dir. Called from both resume paths (interactive
  run() and the -q single-query path).
- Robust degradation: no-op when no cwd recorded, when already there, or
  when the dir is gone (single dim warning, stays put — no crash).
This commit is contained in:
Teknium
2026-06-03 17:37:27 -07:00
committed by GitHub
parent 5446153c98
commit e7bc6189cf
2 changed files with 83 additions and 1 deletions
+55
View File
@@ -5108,6 +5108,7 @@ class HermesCLI:
f"[bold {_accent_hex()}]{_escape(title_part)}[/] "
f"({msg_count} user message{'s' if msg_count != 1 else ''}, {len(restored)} total messages)"
)
self._restore_session_cwd(session_meta, quiet=_quiet_mode)
else:
if _quiet_mode:
print(
@@ -5327,6 +5328,59 @@ class HermesCLI:
self._console_print()
def _restore_session_cwd(self, session_meta: dict, *, quiet: bool = False) -> None:
"""Relaunch a resumed session in the directory it was started from.
Idempotent and safe to call from every resume path. When the stored
``cwd`` differs from the current process directory, we both
``os.chdir()`` (so the process and any ``os.getcwd()`` fallback agree)
and retarget ``TERMINAL_CWD`` (so the terminal tool, code-exec tool,
and relative-path resolution all land in the same place the local
terminal backend snapshots cwd on first use, which happens after this).
No-ops when: the session recorded no cwd (gateway/remote/older
sessions), the directory no longer exists, or we're already there.
A missing directory degrades to a single dim warning rather than a
crash repos get moved and deleted.
"""
recorded = (session_meta or {}).get("cwd")
if not recorded:
return
recorded = os.path.expanduser(str(recorded))
try:
current = os.getcwd()
except OSError:
current = None
if current and os.path.realpath(recorded) == os.path.realpath(current):
return # Already where the session lived — nothing to announce.
if not os.path.isdir(recorded):
msg = f"⚠ Session's working directory is gone: {recorded} — staying in {current or '.'}"
if quiet:
print(msg, file=sys.stderr)
else:
self._console_print(f"[{_DIM}]{_escape(msg)}[/]")
return
try:
os.chdir(recorded)
except OSError as e:
msg = f"⚠ Could not enter session's working directory {recorded}: {e}"
if quiet:
print(msg, file=sys.stderr)
else:
self._console_print(f"[{_DIM}]{_escape(msg)}[/]")
return
# Retarget the terminal/code-exec tools to match the process cwd.
os.environ["TERMINAL_CWD"] = recorded
msg = f"↻ Working directory: {recorded}"
if quiet:
print(msg, file=sys.stderr)
else:
self._console_print(f"[{_DIM}]{_escape(msg)}[/]")
def _preload_resumed_session(self) -> bool:
"""Load a resumed session's history from the DB early (before first chat).
@@ -5383,6 +5437,7 @@ class HermesCLI:
f"({msg_count} user message{'s' if msg_count != 1 else ''}, "
f"{len(restored)} total messages)[/]"
)
self._restore_session_cwd(session_meta)
else:
accent_color = _accent_hex()
self._console_print(