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:
2026-06-10 21:31:42 -04:00
parent 3da3fc6c4c
commit 5265c8d4b0
2 changed files with 60 additions and 0 deletions
+27
View File
@@ -440,6 +440,33 @@ class SessionService:
raise ServiceError(f"{window_name!r} is not an hqt session window")
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:
with self.factory() as db:
sess = db.get(Session, session_id)
+33
View File
@@ -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):
with pytest.raises(ServiceError):
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")