mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(gateway): record a terminal session's working directory
Sessions started from the terminal were stored with no `cwd` and no `git_repo_root`, so the sidebar had nothing to group them by and they never appeared under their project — they fell into "No workspace" instead. On one real profile this covered 690 of 1063 TUI sessions. The row write persisted a cwd only when `explicit_cwd` was set, which happens only if the user switches directory mid-session. The intent was to avoid filing chats under whatever folder the app launched in, and that reasoning holds for the desktop, whose launch directory is an artifact of how the bundle was opened. It does not hold for a terminal session: the user cd'd into that directory before running hermes, and it is where the agent's terminal runs. Split the two cases behind one helper. An explicit pick is always persisted; otherwise the launch directory is recorded for terminal-started sessions and left unset for the desktop, preserving the existing "No workspace" default there. Existing rows are unaffected: they carry neither cwd nor git_repo_root, so there is nothing to recover them from.
This commit is contained in:
parent
19025eb7ce
commit
3172b8739e
2 changed files with 57 additions and 7 deletions
|
|
@ -295,6 +295,30 @@ def test_scan_time_is_not_treated_as_session_activity(tmp_path):
|
|||
assert active["last_active"] > idle["last_active"]
|
||||
|
||||
|
||||
def test_terminal_session_persists_its_launch_cwd():
|
||||
"""A terminal session's cwd IS its workspace, so the row must record it.
|
||||
|
||||
The user cd'd into that directory before running hermes. Dropping it left
|
||||
the row with no cwd and no git_repo_root, so the sidebar could never place
|
||||
the session under its project.
|
||||
"""
|
||||
for source in ("tui", "cli"):
|
||||
assert server._persisted_session_cwd(
|
||||
{"source": source, "cwd": "/somewhere/a-repo"}
|
||||
) == "/somewhere/a-repo"
|
||||
|
||||
|
||||
def test_desktop_launch_cwd_is_not_persisted_as_a_workspace():
|
||||
# The desktop launches from wherever the bundle was opened, so an unpicked
|
||||
# cwd is an artifact — those chats belong under "No workspace".
|
||||
assert server._persisted_session_cwd({"source": "desktop", "cwd": "/opt/whatever"}) is None
|
||||
|
||||
# An explicit pick is always honored, desktop included.
|
||||
assert server._persisted_session_cwd(
|
||||
{"source": "desktop", "cwd": "/picked/repo", "explicit_cwd": True}
|
||||
) == "/picked/repo"
|
||||
|
||||
|
||||
def test_disabled_discovery_clears_cache_and_rejects_new_scan(monkeypatch, tmp_path):
|
||||
repo = tmp_path / "cached-repo"
|
||||
repo.mkdir()
|
||||
|
|
|
|||
|
|
@ -2086,6 +2086,25 @@ def _session_cwd(session: dict | None) -> str:
|
|||
return _completion_cwd()
|
||||
|
||||
|
||||
# Sources whose launch directory is an artifact of how the app was started, not
|
||||
# a workspace the user picked. Everything else is terminal-started: the process
|
||||
# runs in a directory the user deliberately cd'd into.
|
||||
_LAUNCH_CWD_NOT_A_WORKSPACE = {"desktop"}
|
||||
|
||||
|
||||
def _persisted_session_cwd(session: dict) -> str | None:
|
||||
"""The cwd to stamp on the session's DB row, or None to leave it unset.
|
||||
|
||||
See :func:`_ensure_session_db_row` for why the launch directory counts as a
|
||||
workspace for terminal sessions but not for the desktop.
|
||||
"""
|
||||
if session.get("explicit_cwd"):
|
||||
return _session_cwd(session)
|
||||
if _session_source(session) in _LAUNCH_CWD_NOT_A_WORKSPACE:
|
||||
return None
|
||||
return _session_cwd(session) or None
|
||||
|
||||
|
||||
def _heal_dead_cwd(cwd: str) -> str:
|
||||
"""Resolve a session cwd that points at a now-deleted directory.
|
||||
|
||||
|
|
@ -2184,12 +2203,19 @@ def _ensure_session_db_row(session: dict) -> None:
|
|||
Uses INSERT OR IGNORE under the hood, so re-calls (and the AIAgent's own
|
||||
lazy create) are no-ops.
|
||||
|
||||
Only an *explicitly chosen* workspace is persisted as the session's cwd.
|
||||
The agent still runs in the auto-detected directory (session["cwd"]), but
|
||||
we don't stamp that onto the row — otherwise every session the user never
|
||||
picked a folder for gets grouped under whatever directory the desktop
|
||||
happened to launch in (e.g. "desktop"). Leaving it null groups them under
|
||||
"No workspace", which is the desired default.
|
||||
A cwd the user *chose* is always persisted. When they made no explicit
|
||||
choice the launch directory stands in, and whether that is meaningful
|
||||
depends on how the session was started:
|
||||
|
||||
* The desktop launches from wherever the app bundle was opened (often ``/``
|
||||
or the user's home), so stamping that would file every unpicked chat under
|
||||
a folder the user never chose. Those stay null and group under "No
|
||||
workspace", which is the desired default.
|
||||
* A terminal session (``hermes`` / ``hermes --tui`` / CLI) is started from a
|
||||
directory the user deliberately ``cd``'d into — that IS the workspace, and
|
||||
it is also where the agent's terminal actually runs. Dropping it stranded
|
||||
the session with no cwd AND no git_repo_root, so the sidebar could never
|
||||
place it under its project.
|
||||
"""
|
||||
key = session.get("session_key")
|
||||
if not key:
|
||||
|
|
@ -2278,7 +2304,7 @@ def _ensure_session_db_row(session: dict) -> None:
|
|||
model=row_model,
|
||||
model_config=model_config or None,
|
||||
parent_session_id=parent_session_id,
|
||||
cwd=_session_cwd(session) if session.get("explicit_cwd") else None,
|
||||
cwd=_persisted_session_cwd(session),
|
||||
# Self-describing rows: aggregators that merge multiple profile DBs
|
||||
# into one list can't rely on which file a row came from alone. NULL
|
||||
# means the launch/default profile (matches run_agent's convention).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue