diff --git a/src/hqt/sessions/service.py b/src/hqt/sessions/service.py index 44fdf62..0294371 100644 --- a/src/hqt/sessions/service.py +++ b/src/hqt/sessions/service.py @@ -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- 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) diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 12d5563..ee2bf0b 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -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")