Add SessionService.clone_session_for_window
Opens a fresh harness session with the same project, harness, and model as the source window, creating a new sibling hqt-<id> conversation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -440,6 +440,33 @@ class SessionService:
|
|||||||
raise ServiceError(f"{window_name!r} is not an hqt session window")
|
raise ServiceError(f"{window_name!r} is not an hqt session window")
|
||||||
return await self.open_tool_window(session_id, tool)
|
return await self.open_tool_window(session_id, tool)
|
||||||
|
|
||||||
|
async def clone_session_for_window(
|
||||||
|
self, window_name: str
|
||||||
|
) -> "CreateSessionResult":
|
||||||
|
"""Open a fresh harness session cloning the one in `window_name`.
|
||||||
|
|
||||||
|
Same project, harness, and model as the source session, but a brand-new
|
||||||
|
conversation (a new hqt-<id> window at the next index). Raises
|
||||||
|
ServiceError if `window_name` is not an active hqt session window — so
|
||||||
|
invoking clone from a tool window (nvim/shell) or the TUI is a clean
|
||||||
|
no-op.
|
||||||
|
"""
|
||||||
|
with self.factory() as db:
|
||||||
|
sess = (
|
||||||
|
db.query(Session)
|
||||||
|
.options(selectinload(Session.harness))
|
||||||
|
.filter_by(tmux_session_name=window_name, archived=False)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if sess is None:
|
||||||
|
raise ServiceError(f"{window_name!r} is not an hqt session window")
|
||||||
|
project_id = sess.project_id
|
||||||
|
harness_name = sess.harness.name
|
||||||
|
model = sess.model
|
||||||
|
return await self.create_session(
|
||||||
|
project_id, harness_name, nickname=None, model=model
|
||||||
|
)
|
||||||
|
|
||||||
async def stop_session(self, session_id: int) -> None:
|
async def stop_session(self, session_id: int) -> None:
|
||||||
with self.factory() as db:
|
with self.factory() as db:
|
||||||
sess = db.get(Session, session_id)
|
sess = db.get(Session, session_id)
|
||||||
|
|||||||
@@ -1601,3 +1601,36 @@ async def test_open_tool_window_for_window_resolves_by_name(
|
|||||||
async def test_open_tool_window_for_window_unknown_window_raises(service):
|
async def test_open_tool_window_for_window_unknown_window_raises(service):
|
||||||
with pytest.raises(ServiceError):
|
with pytest.raises(ServiceError):
|
||||||
await service.open_tool_window_for_window("not-an-hqt-window", "nvim")
|
await service.open_tool_window_for_window("not-an-hqt-window", "nvim")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Task 5: clone_session_for_window
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clone_session_for_window_reuses_project_harness_model(
|
||||||
|
service, db, monkeypatch
|
||||||
|
):
|
||||||
|
await service.create_session(
|
||||||
|
project_id=1, harness_name="claude-code", nickname="orig", model="opus"
|
||||||
|
)
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
async def fake_create(project_id, harness_name, nickname=None, model=None):
|
||||||
|
captured["args"] = (project_id, harness_name, nickname, model)
|
||||||
|
return "SENTINEL"
|
||||||
|
|
||||||
|
monkeypatch.setattr(service, "create_session", fake_create)
|
||||||
|
|
||||||
|
result = await service.clone_session_for_window("hqt-1")
|
||||||
|
|
||||||
|
assert result == "SENTINEL"
|
||||||
|
# Same project + harness + model; a fresh sibling, so no nickname.
|
||||||
|
assert captured["args"] == (1, "claude-code", None, "opus")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_clone_session_for_window_unknown_window_raises(service):
|
||||||
|
with pytest.raises(ServiceError):
|
||||||
|
await service.clone_session_for_window("not-an-hqt-window")
|
||||||
|
|||||||
Reference in New Issue
Block a user