fix(cron): retire persisted heartbeat/dream jobs when disabled

Disabling gateway.heartbeat (or agents.defaults.dream) only skipped
job registration on startup; the previously persisted system job in
<workspace>/cron/jobs.json kept firing, and remove_job refuses to
touch protected system jobs. Add CronService.remove_system_job for
startup reconciliation and call it from the gateway disabled branches
so the config toggle takes effect after restart.
This commit is contained in:
aiguozhi123456
2026-08-22 02:38:47 +08:00
committed by chengyongru
parent 1fe36d5dec
commit e5718d4de6
5 changed files with 61 additions and 0 deletions
+5
View File
@@ -785,6 +785,7 @@ def _run_gateway(
console.print(f"[green]✓[/green] Dream: {dream_cfg.describe_schedule()}")
else:
console.print("[yellow]○[/yellow] Dream: disabled")
cron.remove_system_job("dream")
_advance_dream_cursor_if_behind(agent.context.memory)
# Register Heartbeat system job (idempotent on restart)
@@ -799,6 +800,10 @@ def _run_gateway(
),
payload=CronPayload(kind="system_event"),
))
else:
# Retire any previously persisted heartbeat job so that disabling
# gateway.heartbeat in config takes effect after restart.
cron.remove_system_job("heartbeat")
async def _open_browser_when_ready() -> None:
"""Wait for the gateway to bind, then point the user's browser at the webui."""
+19
View File
@@ -718,6 +718,25 @@ class CronService:
logger.info("Cron: registered system job '{}' ({})", job.name, job.id)
return job
def remove_system_job(self, job_id: str) -> bool:
"""Remove an internal system job by id (startup reconciliation).
Unlike ``remove_job``, this bypasses the protected-system-job guard:
the gateway retires a persisted system job whose config has been
disabled, so e.g. ``gateway.heartbeat.enabled=false`` actually takes
effect after restart instead of the leftover job firing forever.
Returns True when a job was removed.
"""
store = self._require_store()
before = len(store.jobs)
store.jobs = [j for j in store.jobs if j.id != job_id]
removed = len(store.jobs) < before
if removed:
self._save_store()
self._arm_timer()
logger.info("Cron: removed system job {}", job_id)
return removed
def remove_job(self, job_id: str) -> Literal["removed", "protected", "not_found"]:
"""Remove a job by ID, unless it is a protected system job."""
store = self._require_store()