diff --git a/src/hqt/sessions/service.py b/src/hqt/sessions/service.py index 84395fc..1f4579c 100644 --- a/src/hqt/sessions/service.py +++ b/src/hqt/sessions/service.py @@ -423,7 +423,8 @@ class SessionService: self, session_id: int, prompt: str, due_at: datetime ) -> ScheduledPrompt: """Create or replace the pending scheduled prompt for a session.""" - if not prompt.strip(): + prompt = prompt.strip() + if not prompt: raise ServiceError("Prompt cannot be empty") with self.factory() as db: sess = db.get(Session, session_id) @@ -504,6 +505,11 @@ class SessionService: force, ) + scheduled = ( + db.query(ScheduledPrompt).filter_by(session_id=session_id).one_or_none() + ) + if scheduled is not None: + db.delete(scheduled) db.delete(sess) db.commit() diff --git a/tests/test_sessions.py b/tests/test_sessions.py index e3f5d04..d645222 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -100,7 +100,7 @@ def test_schedule_prompt_creates_pending_row(service, db): sess = _seed_session(db) due_at = datetime(2026, 6, 11, 12) - prompt = service.schedule_prompt(sess.id, "run the tests", due_at) + prompt = service.schedule_prompt(sess.id, " run the tests ", due_at) assert prompt.session_id == sess.id assert prompt.prompt == "run the tests" @@ -704,6 +704,27 @@ async def test_sync_window_labels_covers_all_projects(factory, db, tmux, harness assert labelled == {"hqt-1", "hqt-2"} +@pytest.mark.asyncio +async def test_sync_window_labels_includes_pending_scheduled_prompt( + factory, db, tmux, harnesses +): + """sync_window_labels returns SessionInfo with pending scheduled prompt metadata.""" + from hqt.tmux.runner import WindowInfo + + service = SessionService(factory=factory, tmux=tmux, harnesses=harnesses) + result = await service.create_session(project_id=1, harness_name="claude-code") + due_at = datetime(2026, 6, 11, 12) + service.schedule_prompt(result.session.id, "run the tests", due_at) + tmux.poll_info.return_value = {"hqt-1": WindowInfo(alive=False, last_activity=0)} + tmux.set_window_label = AsyncMock() + + infos = await service.sync_window_labels() + + assert infos[0].scheduled_prompt is not None + assert infos[0].scheduled_prompt.prompt == "run the tests" + assert infos[0].scheduled_prompt.status == "pending" + + # --------------------------------------------------------------------------- # Item 3: real-configurator integration-seam test # --------------------------------------------------------------------------- @@ -1499,6 +1520,20 @@ async def test_delete_session_remove_worktree_false_skips_removal( assert db.get(Session, sess_id) is None +@pytest.mark.asyncio +async def test_delete_session_deletes_scheduled_prompt(service, db): + sess = _seed_session(db) + sess_id = sess.id + due_at = datetime(2026, 6, 11, 12) + service.schedule_prompt(sess_id, "run the tests", due_at) + + await service.delete_session(sess_id) + + db.expire_all() + assert db.get(Session, sess_id) is None + assert db.query(ScheduledPrompt).filter_by(session_id=sess_id).one_or_none() is None + + @pytest.mark.asyncio async def test_worktree_state_for_plain_session_returns_none( service, db, tmux, harnesses, fake_worktree