mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
fix(gateway): new chats honor their profile in global-remote mode (#39993)
Follow-up to #39921. That PR scoped session.resume + prompt.submit to a session's profile, but a BRAND-NEW chat (session.create) under a non-launch profile was still built and persisted against the dashboard's launch profile. Two visible symptoms in app-global remote mode (one dashboard, many profiles): 1. "who are you" in profile S replied as the launch (default) profile/agent — the agent was built with the launch HERMES_HOME, so config/SOUL/identity came from the wrong profile. 2. "session not found" on later resume — _ensure_session_db_row persisted the row into the launch profile's state.db via _get_db(), so the session lived in the wrong db, the unified list mis-tagged it (it showed up under BOTH profiles), and resume routed to the wrong one. Fix — carry the owning profile through the create path too: - session.create accepts an optional `profile`; resolves its home and stores `profile_home` on the session (alongside what resume already set). - _start_agent_build binds that profile's HERMES_HOME while building the agent (config/skills/model/identity resolve to it) and hands the agent the profile's state.db so turns persist there. - _ensure_session_db_row writes the row into the profile's state.db, not the launch db — fixing the duplicate row + mis-tag + resume 404. - desktop sends the new-chat profile on session.create. None/launch profile → unchanged (single-profile and per-profile-remote setups take the same path). Verified live against a one-dashboard / multi-profile remote: a new chat under `work` builds as work's agent (correct SOUL identity), persists ONLY to work's state.db (launch db stays empty), the unified list tags it `work` exactly once, and it resumes cleanly. tests/test_tui_gateway_server.py: _make_agent mocks updated for the session_db param added in #39921's build path.
This commit is contained in:
parent
1d9c3ebae0
commit
6f6eb871d8
3 changed files with 58 additions and 6 deletions
|
|
@ -331,7 +331,14 @@ export function useSessionActions({
|
|||
// so single-profile users are unaffected).
|
||||
await ensureGatewayProfile($newChatProfile.get())
|
||||
const cwd = $currentCwd.get().trim() || getRememberedWorkspaceCwd()
|
||||
const created = await requestGateway<SessionCreateResponse>('session.create', { cols: 96, ...(cwd && { cwd }) })
|
||||
// Pass the owning profile so a new chat under a non-launch profile (global
|
||||
// remote mode) builds its agent + persists against THAT profile's home/db.
|
||||
const newChatProfile = $newChatProfile.get()
|
||||
const created = await requestGateway<SessionCreateResponse>('session.create', {
|
||||
cols: 96,
|
||||
...(cwd && { cwd }),
|
||||
...(newChatProfile ? { profile: newChatProfile } : {})
|
||||
})
|
||||
const stored = created.stored_session_id ?? null
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -3577,7 +3577,7 @@ def test_session_create_close_race_does_not_orphan_worker(monkeypatch):
|
|||
release_build = threading.Event()
|
||||
build_entered = threading.Event()
|
||||
|
||||
def _slow_make_agent(sid, key, session_id=None):
|
||||
def _slow_make_agent(sid, key, session_id=None, session_db=None):
|
||||
build_started.set()
|
||||
build_entered.set()
|
||||
release_build.wait(timeout=3.0)
|
||||
|
|
@ -3685,7 +3685,7 @@ def test_session_create_no_race_keeps_worker_alive(monkeypatch):
|
|||
self.base_url = ""
|
||||
self.api_key = ""
|
||||
|
||||
monkeypatch.setattr(server, "_make_agent", lambda sid, key: _FakeAgent())
|
||||
monkeypatch.setattr(server, "_make_agent", lambda sid, key, session_db=None: _FakeAgent())
|
||||
monkeypatch.setattr(server, "_SlashWorker", _FakeWorker)
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
|
|
@ -3769,7 +3769,7 @@ def test_session_create_continues_when_state_db_is_unavailable(monkeypatch):
|
|||
|
||||
emits = []
|
||||
|
||||
monkeypatch.setattr(server, "_make_agent", lambda sid, key: _FakeAgent())
|
||||
monkeypatch.setattr(server, "_make_agent", lambda sid, key, session_db=None: _FakeAgent())
|
||||
monkeypatch.setattr(server, "_SlashWorker", _FakeWorker)
|
||||
monkeypatch.setattr(server, "_get_db", lambda: None)
|
||||
monkeypatch.setattr(server, "_session_info", lambda _a, *a2: {"model": "x"})
|
||||
|
|
|
|||
|
|
@ -678,10 +678,24 @@ def _start_agent_build(sid: str, session: dict) -> None:
|
|||
|
||||
worker = None
|
||||
notify_registered = False
|
||||
home_token = None
|
||||
profile_home = current.get("profile_home")
|
||||
try:
|
||||
tokens = _set_session_context(key)
|
||||
# Build against the session's profile (global-remote): bind its
|
||||
# HERMES_HOME so config/skills/model resolve to it, and hand the
|
||||
# agent that profile's db so turns persist to the right state.db.
|
||||
session_db = None
|
||||
if profile_home:
|
||||
home_token = set_hermes_home_override(profile_home)
|
||||
try:
|
||||
from hermes_state import SessionDB
|
||||
|
||||
session_db = SessionDB(db_path=Path(profile_home) / "state.db")
|
||||
except Exception:
|
||||
session_db = None
|
||||
try:
|
||||
agent = _make_agent(sid, key)
|
||||
agent = _make_agent(sid, key, session_db=session_db)
|
||||
finally:
|
||||
_clear_session_context(tokens)
|
||||
|
||||
|
|
@ -725,6 +739,8 @@ def _start_agent_build(sid: str, session: dict) -> None:
|
|||
current["agent_error"] = str(e)
|
||||
_emit("error", sid, {"message": f"agent init failed: {e}"})
|
||||
finally:
|
||||
if home_token is not None:
|
||||
reset_hermes_home_override(home_token)
|
||||
with _sessions_lock:
|
||||
replaced = _sessions.get(sid) is not current
|
||||
if replaced:
|
||||
|
|
@ -850,7 +866,22 @@ def _ensure_session_db_row(session: dict) -> None:
|
|||
key = session.get("session_key")
|
||||
if not key:
|
||||
return
|
||||
db = _get_db()
|
||||
# Persist into the session's own profile db (global remote mode), not the
|
||||
# launch profile's — otherwise the row lands in the wrong state.db, the
|
||||
# unified list mis-tags it, and resume 404s ("session not found").
|
||||
profile_home = session.get("profile_home")
|
||||
if profile_home:
|
||||
from hermes_state import SessionDB
|
||||
|
||||
try:
|
||||
db = SessionDB(db_path=Path(profile_home) / "state.db")
|
||||
except Exception:
|
||||
logger.debug("failed to open profile db for session row", exc_info=True)
|
||||
return
|
||||
close_db = True
|
||||
else:
|
||||
db = _get_db()
|
||||
close_db = False
|
||||
if db is None:
|
||||
return
|
||||
try:
|
||||
|
|
@ -862,6 +893,12 @@ def _ensure_session_db_row(session: dict) -> None:
|
|||
)
|
||||
except Exception:
|
||||
logger.debug("failed to persist desktop session row", exc_info=True)
|
||||
finally:
|
||||
if close_db:
|
||||
try:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _set_session_cwd(session: dict, cwd: str) -> str:
|
||||
|
|
@ -2960,6 +2997,13 @@ def _(rid, params: dict) -> dict:
|
|||
resolved_cwd = _completion_cwd(params)
|
||||
_enable_gateway_prompts()
|
||||
|
||||
# ``profile`` (app-global remote mode): a new chat started under a non-launch
|
||||
# profile must build its agent + persist against THAT profile's home/state.db,
|
||||
# not the dashboard's launch profile. Stored on the session so _start_agent_build
|
||||
# and each turn re-bind HERMES_HOME. None/own profile → launch (unchanged).
|
||||
profile = (params.get("profile") or "").strip() or None
|
||||
profile_home = _profile_home(profile)
|
||||
|
||||
ready = threading.Event()
|
||||
now = time.time()
|
||||
|
||||
|
|
@ -2981,6 +3025,7 @@ def _(rid, params: dict) -> dict:
|
|||
"inflight_turn": None,
|
||||
"last_active": now,
|
||||
"pending_title": title or None,
|
||||
"profile_home": str(profile_home) if profile_home is not None else None,
|
||||
"running": False,
|
||||
"session_key": key,
|
||||
"show_reasoning": _load_show_reasoning(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue