fix(prompt): preserve resumed workspace provenance
This commit is contained in:
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -511,6 +512,51 @@ def test_explicit_desktop_and_terminal_cwds_are_context_workspaces():
|
||||
) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("explicit_cwd", "launch_artifact"),
|
||||
[(True, False), (False, True)],
|
||||
)
|
||||
def test_desktop_agent_rebuild_preserves_workspace_provenance(
|
||||
monkeypatch, explicit_cwd, launch_artifact
|
||||
):
|
||||
captured = {}
|
||||
session = {
|
||||
"agent": object(),
|
||||
"attached_images": [],
|
||||
"cwd": "/picked/repo" if explicit_cwd else "/opt/hermes",
|
||||
"edit_snapshots": {},
|
||||
"explicit_cwd": explicit_cwd,
|
||||
"history": ["old"],
|
||||
"history_lock": threading.Lock(),
|
||||
"history_version": 0,
|
||||
"image_counter": 0,
|
||||
"running": False,
|
||||
"session_key": "stored-session",
|
||||
"show_reasoning": False,
|
||||
"source": "desktop",
|
||||
"tool_progress_mode": "off",
|
||||
"tool_started_at": {},
|
||||
}
|
||||
|
||||
def _make_agent(*_args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return object()
|
||||
|
||||
monkeypatch.setattr(server, "_set_session_context", lambda _key: [])
|
||||
monkeypatch.setattr(server, "_clear_session_context", lambda _tokens: None)
|
||||
monkeypatch.setattr(server, "_make_agent", _make_agent)
|
||||
monkeypatch.setattr(server, "_config_model_target", lambda: None)
|
||||
monkeypatch.setattr(server, "_load_show_reasoning", lambda: False)
|
||||
monkeypatch.setattr(server, "_load_tool_progress_mode", lambda: "off")
|
||||
monkeypatch.setattr(server, "_session_info", lambda *_args: {})
|
||||
monkeypatch.setattr(server, "_emit", lambda *_args: None)
|
||||
monkeypatch.setattr(server, "_restart_slash_worker", lambda *_args: None)
|
||||
|
||||
server._reset_session_agent("live-session", session)
|
||||
|
||||
assert captured["context_cwd_is_launch_artifact"] is launch_artifact
|
||||
|
||||
|
||||
def test_home_container_dirs_are_never_a_workspace(tmp_path):
|
||||
"""`/home` and `/Users` hold homes; they are not workspaces themselves.
|
||||
|
||||
|
||||
@@ -368,6 +368,34 @@ def test_deferred_build_transfers_the_handle_on_success(
|
||||
assert session["agent"]._owns_session_db is True
|
||||
|
||||
|
||||
def test_deferred_build_uses_the_preserved_desktop_workspace_provenance(
|
||||
build_env, registered, monkeypatch
|
||||
):
|
||||
captured: dict = {}
|
||||
|
||||
def _fake_make_agent(*_args, session_db=None, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return types.SimpleNamespace(_session_db=session_db, _owns_session_db=False)
|
||||
|
||||
monkeypatch.setattr(server, "_make_agent", _fake_make_agent)
|
||||
monkeypatch.setattr(
|
||||
server, "_session_source", lambda current: current.get("source")
|
||||
)
|
||||
sid, session = "sid-workspace", _session(build_env.profile_home)
|
||||
session.update(
|
||||
{
|
||||
"cwd": "C:/picked/repo",
|
||||
"explicit_cwd": True,
|
||||
"source": "desktop",
|
||||
}
|
||||
)
|
||||
registered(sid, session)
|
||||
|
||||
_run_build(sid, session)
|
||||
|
||||
assert captured["context_cwd_is_launch_artifact"] is False
|
||||
|
||||
|
||||
def test_deferred_build_closes_the_handle_when_the_session_is_reaped_midbuild(
|
||||
build_env, registered, monkeypatch
|
||||
):
|
||||
|
||||
@@ -134,6 +134,28 @@ def test_resume_closes_profile_db_when_session_not_found(profile_dbs):
|
||||
assert scoped[0].closed == 1
|
||||
|
||||
|
||||
def test_deferred_desktop_resume_keeps_stored_workspace_provenance(
|
||||
profile_dbs, monkeypatch, tmp_path
|
||||
):
|
||||
workspace = tmp_path / "workspace"
|
||||
workspace.mkdir()
|
||||
|
||||
def _factory(db_path=None, **kwargs):
|
||||
db = _RecordingDB(db_path=db_path, **kwargs)
|
||||
db.rows["s1"] = {"id": "s1", "cwd": str(workspace)}
|
||||
profile_dbs.append(db)
|
||||
return db
|
||||
|
||||
monkeypatch.setattr("hermes_state.SessionDB", _factory)
|
||||
|
||||
resp = _resume(session_id="s1", profile="work", source="desktop")
|
||||
session = server._sessions[resp["result"]["session_id"]]
|
||||
|
||||
assert session["cwd"] == str(workspace)
|
||||
assert session["explicit_cwd"] is True
|
||||
assert server._context_cwd_is_launch_artifact(session) is False
|
||||
|
||||
|
||||
def test_resume_closes_profile_db_when_reopen_fails(profile_dbs, monkeypatch):
|
||||
"""The 'resume failed' early return must not leak the handle."""
|
||||
|
||||
|
||||
@@ -689,6 +689,7 @@ def _(rid, params: dict) -> dict:
|
||||
profile_home=profile_home,
|
||||
lazy=True,
|
||||
todo_state=_todo_state_from_history(history),
|
||||
explicit_cwd=bool(profile_resume_cwd),
|
||||
)
|
||||
if (live := _claim_or_reuse_live(sid, target, record, lease)) is not None:
|
||||
return _reuse_live_response(*live)
|
||||
@@ -760,6 +761,7 @@ def _(rid, params: dict) -> dict:
|
||||
profile_home=profile_home,
|
||||
model_override=overrides.get("model_override"),
|
||||
resume_runtime_overrides=overrides or None,
|
||||
explicit_cwd=bool(profile_resume_cwd),
|
||||
)
|
||||
record["resume_history_ready"] = threading.Event()
|
||||
record["resume_hydrating"] = True
|
||||
@@ -860,6 +862,7 @@ def _(rid, params: dict) -> dict:
|
||||
model_override=overrides.get("model_override"),
|
||||
resume_runtime_overrides=overrides or None,
|
||||
todo_state=_todo_state_from_history(history),
|
||||
explicit_cwd=bool(profile_resume_cwd),
|
||||
)
|
||||
if (live := _claim_or_reuse_live(sid, target, record, lease)) is not None:
|
||||
return _reuse_live_response(*live)
|
||||
@@ -998,6 +1001,7 @@ def _(rid, params: dict) -> dict:
|
||||
cwd=profile_resume_cwd,
|
||||
session_db=db,
|
||||
source=source,
|
||||
explicit_cwd=bool(profile_resume_cwd),
|
||||
)
|
||||
# Ownership TRANSFER — the registered session's agent now
|
||||
# holds this handle for its whole life, and _init_session
|
||||
@@ -3324,6 +3328,7 @@ def _(rid, params: dict) -> dict:
|
||||
session_db=branch_db,
|
||||
source=source,
|
||||
profile_home=parent_home,
|
||||
explicit_cwd=bool(session.get("explicit_cwd")),
|
||||
)
|
||||
# Ownership TRANSFER — the branched session's agent holds this
|
||||
# handle for its whole life and closes it on teardown. Drop is
|
||||
|
||||
+13
-2
@@ -3221,7 +3221,12 @@ def _start_agent_build(sid: str, session: dict) -> None:
|
||||
# Lazy-resumed (watch) sessions carry the stored conversation
|
||||
# id — pass it through so the upgrade continues that session
|
||||
# instead of starting a fresh one under the same key.
|
||||
kw = {"session_db": session_db}
|
||||
kw = {
|
||||
"session_db": session_db,
|
||||
"context_cwd_is_launch_artifact": (
|
||||
_context_cwd_is_launch_artifact(current)
|
||||
),
|
||||
}
|
||||
if resume_sid := current.get("resume_session_id"):
|
||||
kw["session_id"] = resume_sid
|
||||
kw["platform_override"] = _session_source(current)
|
||||
@@ -8672,6 +8677,9 @@ def _reset_session_agent(sid: str, session: dict) -> dict:
|
||||
session["session_key"],
|
||||
session_id=session["session_key"],
|
||||
platform_override=_session_source(session),
|
||||
context_cwd_is_launch_artifact=(
|
||||
_context_cwd_is_launch_artifact(session)
|
||||
),
|
||||
)
|
||||
finally:
|
||||
_clear_session_context(tokens)
|
||||
@@ -9041,6 +9049,7 @@ def _init_session(
|
||||
session_db=None,
|
||||
source: str | None = None,
|
||||
profile_home: str | None = None,
|
||||
explicit_cwd: bool = False,
|
||||
):
|
||||
now = time.time()
|
||||
with _sessions_lock:
|
||||
@@ -9057,6 +9066,7 @@ def _init_session(
|
||||
"attached_images": [],
|
||||
"image_counter": 0,
|
||||
"cwd": cwd or _completion_cwd(),
|
||||
"explicit_cwd": bool(explicit_cwd),
|
||||
"cols": cols,
|
||||
"slash_worker": None,
|
||||
"show_reasoning": _load_show_reasoning(),
|
||||
@@ -10462,6 +10472,7 @@ def _deferred_session_record(
|
||||
model_override=None,
|
||||
resume_runtime_overrides: dict | None = None,
|
||||
todo_state: dict | None = None,
|
||||
explicit_cwd: bool = False,
|
||||
) -> dict:
|
||||
"""A live-session record whose AIAgent is built later (lazy watch / cold
|
||||
resume) — _init_session's shape minus the agent."""
|
||||
@@ -10478,7 +10489,7 @@ def _deferred_session_record(
|
||||
"cwd": cwd,
|
||||
"display_history_prefix": display_history_prefix or [],
|
||||
"edit_snapshots": {},
|
||||
"explicit_cwd": False,
|
||||
"explicit_cwd": bool(explicit_cwd),
|
||||
"history": history,
|
||||
"history_lock": threading.Lock(),
|
||||
"history_version": 0,
|
||||
|
||||
Reference in New Issue
Block a user