diff --git a/cron/scheduler.py b/cron/scheduler.py index 38b7b95ab7f..f5c71ceed4f 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -1973,6 +1973,18 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]: for _var_name in _cron_delivery_vars: _VAR_MAP[_var_name].set("") if _session_db: + # Title the cron session from the job (name → short prompt → id) so + # sidebars/history show a meaningful label instead of the injected + # "[IMPORTANT: …]" hint that is the session's first message. Set here + # (not at create time) so the agent's own INSERT keeps model / + # system_prompt; this only UPDATEs the title column. The run-time + # suffix keeps it unique against the sessions.title index across runs. + try: + _title_base = " ".join(job_name.split())[:60].strip() or f"cron {job_id}" + _cron_title = f"{_title_base} · {_hermes_now().strftime('%b %d %H:%M')}" + _session_db.set_session_title(_cron_session_id, _cron_title) + except (Exception, KeyboardInterrupt) as e: + logger.debug("Job '%s': failed to set cron session title: %s", job_id, e) try: _session_db.end_session(_cron_session_id, "cron_complete") except (Exception, KeyboardInterrupt) as e: diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index 432beb764f9..8056d3c49e2 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -912,6 +912,43 @@ class TestRunJobSessionPersistence: fake_db.close.assert_called_once() mock_agent.close.assert_called_once() + def test_run_job_titles_cron_session_from_job_not_important_hint(self, tmp_path): + # The cron session's first message is the injected "[IMPORTANT: …]" + # hint, which used to surface as the sidebar/history row label. run_job + # must title the session from the job (name → short prompt → id). + job = { + "id": "test-job", + "name": "Morning digest", + "prompt": "summarize my inbox", + } + fake_db = MagicMock() + + with patch("cron.scheduler._hermes_home", tmp_path), \ + patch("cron.scheduler._resolve_origin", return_value=None), \ + patch("dotenv.load_dotenv"), \ + patch("hermes_state.SessionDB", return_value=fake_db), \ + patch( + "hermes_cli.runtime_provider.resolve_runtime_provider", + return_value={ + "api_key": "test-key", + "base_url": "https://example.invalid/v1", + "provider": "openrouter", + "api_mode": "chat_completions", + }, + ), \ + patch("run_agent.AIAgent") as mock_agent_cls: + mock_agent = MagicMock() + mock_agent.run_conversation.return_value = {"final_response": "ok"} + mock_agent_cls.return_value = mock_agent + + run_job(job) + + fake_db.set_session_title.assert_called_once() + sid, title = fake_db.set_session_title.call_args[0] + assert sid.startswith("cron_test-job_") + assert "IMPORTANT" not in title + assert title.startswith("Morning digest") + def test_run_job_closes_agent_on_failure_to_prevent_fd_leak(self, tmp_path): # Regression: if ``run_conversation`` raises, the ephemeral cron # agent was previously leaked — over days of ticks this accumulated