fix(gateway): let an explicit workspace move win for a running session
session.workspace.move refused a running live session with 4009 (session busy), but the desktop's Move-to-project flow calls exactly this RPC — so the UI updated its local grouping while state.db kept the old cwd and the agent's tools kept running in the old workspace. Two sources of truth disagreed (#86626). An explicit move now wins: the stored row and the live session re-anchor together. In-flight tool calls keep the cwd they were launched with; the next tool call uses the new workspace.
This commit is contained in:
@@ -19458,3 +19458,55 @@ def test_persist_live_session_system_prompt_restores_pre_existing_override(tmp_p
|
||||
finally:
|
||||
reset_hermes_home_override(outer_token)
|
||||
assert get_hermes_home_override() is None
|
||||
|
||||
|
||||
def test_workspace_move_rehomes_running_session(monkeypatch, tmp_path):
|
||||
"""An explicit Move-to-project must win for a RUNNING session: the stored
|
||||
row and the live runtime session re-anchor together, never a UI-vs-db
|
||||
disagreement (#86626)."""
|
||||
target = "stored-running-session"
|
||||
new_cwd = tmp_path / "dest-project"
|
||||
new_cwd.mkdir()
|
||||
captured = {}
|
||||
|
||||
class FakeDB:
|
||||
def get_session(self, session_id):
|
||||
return {"id": session_id}
|
||||
|
||||
def update_session_cwd(self, session_id, cwd, branch=None, root=None, replace_git_meta=True):
|
||||
captured["row_update"] = (session_id, cwd)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
import contextlib
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _fake_db(_params):
|
||||
yield FakeDB()
|
||||
|
||||
monkeypatch.setattr(server, "_profile_db", _fake_db)
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
"_git_branch_for_cwd",
|
||||
lambda cwd: "main",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
"_git_common_repo_root_for_cwd",
|
||||
lambda cwd: str(new_cwd),
|
||||
)
|
||||
|
||||
live = {"session_key": target, "running": True, "cwd": str(tmp_path / "old-project")}
|
||||
server._sessions["live-sid"] = live
|
||||
monkeypatch.setattr(server, "_register_session_cwd", lambda _session: None)
|
||||
|
||||
res = server._methods["session.workspace.move"](
|
||||
"rid",
|
||||
{"session_key": target, "cwd": str(new_cwd)},
|
||||
)
|
||||
|
||||
assert "error" not in res, res
|
||||
assert captured["row_update"] == (target, str(new_cwd))
|
||||
assert live["cwd"] == str(new_cwd)
|
||||
assert live.get("explicit_cwd") is True
|
||||
|
||||
@@ -920,8 +920,11 @@ def _(rid, params: dict) -> dict:
|
||||
stale ``git_repo_root`` would keep it grouped under the project it left.
|
||||
|
||||
A live agent bound to the row follows through the runtime path too, so its
|
||||
terminal/file tools re-anchor immediately; a mid-turn session refuses the
|
||||
move rather than yanking the workspace out from under its tools.
|
||||
terminal/file tools re-anchor immediately. An explicit move wins even
|
||||
mid-turn: refusing a running session made the desktop's "Move to project"
|
||||
claim success in the UI while ``state.db`` kept the old cwd — two sources
|
||||
of truth disagreeing (#86626). In-flight tool calls keep the cwd they were
|
||||
launched with; the NEXT tool call uses the new workspace.
|
||||
"""
|
||||
target = str(params.get("session_key") or "").strip()
|
||||
if not target:
|
||||
@@ -944,8 +947,6 @@ def _(rid, params: dict) -> dict:
|
||||
if sess.get("session_key") == target:
|
||||
live, live_sid = sess, sid
|
||||
break
|
||||
if live is not None and live.get("running"):
|
||||
return _err(rid, 4009, "session busy")
|
||||
|
||||
branch = _git_branch_for_cwd(resolved)
|
||||
root = _git_common_repo_root_for_cwd(resolved)
|
||||
|
||||
Reference in New Issue
Block a user