mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(cron): normalize partial job records
This commit is contained in:
parent
f2afa68a4a
commit
e407376c50
6 changed files with 122 additions and 11 deletions
70
cron/jobs.py
70
cron/jobs.py
|
|
@ -72,6 +72,65 @@ def _apply_skill_fields(job: Dict[str, Any]) -> Dict[str, Any]:
|
|||
return normalized
|
||||
|
||||
|
||||
def _coerce_job_text(value: Any, fallback: str = "") -> str:
|
||||
"""Coerce legacy/hand-edited nullable cron fields to strings for readers."""
|
||||
if value is None:
|
||||
return fallback
|
||||
return str(value)
|
||||
|
||||
|
||||
def _schedule_display_for_job(job: Dict[str, Any]) -> str:
|
||||
display = _coerce_job_text(job.get("schedule_display")).strip()
|
||||
if display:
|
||||
return display
|
||||
|
||||
schedule = job.get("schedule")
|
||||
if isinstance(schedule, dict):
|
||||
for key in ("display", "value", "expr", "run_at"):
|
||||
text = _coerce_job_text(schedule.get(key)).strip()
|
||||
if text:
|
||||
return text
|
||||
elif schedule is not None:
|
||||
return str(schedule)
|
||||
|
||||
return "?"
|
||||
|
||||
|
||||
def _normalize_job_record(job: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Return a read-safe cron job shape for UI/API/tool/scheduler consumers.
|
||||
|
||||
Older or hand-edited jobs can have nullable fields like ``prompt``,
|
||||
``name``, or ``schedule_display``. Keep storage untouched on read, but
|
||||
ensure consumers never crash while formatting or running those records.
|
||||
"""
|
||||
normalized = _apply_skill_fields(job)
|
||||
job_id = _coerce_job_text(normalized.get("id"), "unknown")
|
||||
prompt = _coerce_job_text(normalized.get("prompt"))
|
||||
normalized["id"] = job_id
|
||||
normalized["prompt"] = prompt
|
||||
|
||||
name = _coerce_job_text(normalized.get("name")).strip()
|
||||
if not name:
|
||||
script = _coerce_job_text(normalized.get("script")).strip()
|
||||
label_source = (
|
||||
prompt
|
||||
or (normalized["skills"][0] if normalized.get("skills") else "")
|
||||
or script
|
||||
or job_id
|
||||
or "cron job"
|
||||
)
|
||||
name = label_source[:50].strip() or "cron job"
|
||||
normalized["name"] = name
|
||||
normalized["schedule_display"] = _schedule_display_for_job(normalized)
|
||||
|
||||
state = _coerce_job_text(normalized.get("state")).strip()
|
||||
if not state:
|
||||
state = "scheduled" if normalized.get("enabled", True) else "paused"
|
||||
normalized["state"] = state
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
def _secure_dir(path: Path):
|
||||
"""Set directory to owner-only access (0700). No-op on Windows."""
|
||||
try:
|
||||
|
|
@ -533,11 +592,12 @@ def create_job(
|
|||
else:
|
||||
context_from = None
|
||||
|
||||
label_source = (prompt or (normalized_skills[0] if normalized_skills else None) or (normalized_script if normalized_no_agent else None)) or "cron job"
|
||||
prompt_text = _coerce_job_text(prompt)
|
||||
label_source = (prompt_text or (normalized_skills[0] if normalized_skills else None) or (normalized_script if normalized_no_agent else None)) or "cron job"
|
||||
job = {
|
||||
"id": job_id,
|
||||
"name": name or label_source[:50].strip(),
|
||||
"prompt": prompt,
|
||||
"prompt": prompt_text,
|
||||
"skills": normalized_skills,
|
||||
"skill": normalized_skills[0] if normalized_skills else None,
|
||||
"model": normalized_model,
|
||||
|
|
@ -581,13 +641,13 @@ def get_job(job_id: str) -> Optional[Dict[str, Any]]:
|
|||
jobs = load_jobs()
|
||||
for job in jobs:
|
||||
if job["id"] == job_id:
|
||||
return _apply_skill_fields(job)
|
||||
return _normalize_job_record(job)
|
||||
return None
|
||||
|
||||
|
||||
def list_jobs(include_disabled: bool = False) -> List[Dict[str, Any]]:
|
||||
"""List all jobs, optionally including disabled ones."""
|
||||
jobs = [_apply_skill_fields(j) for j in load_jobs()]
|
||||
jobs = [_normalize_job_record(j) for j in load_jobs()]
|
||||
if not include_disabled:
|
||||
jobs = [j for j in jobs if j.get("enabled", True)]
|
||||
return jobs
|
||||
|
|
@ -637,7 +697,7 @@ def update_job(job_id: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]
|
|||
|
||||
jobs[i] = updated
|
||||
save_jobs(jobs)
|
||||
return _apply_skill_fields(jobs[i])
|
||||
return _normalize_job_record(jobs[i])
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -845,7 +845,7 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|||
result is used for prompt injection. When omitted, the script
|
||||
(if any) runs inline as before.
|
||||
"""
|
||||
prompt = job.get("prompt", "")
|
||||
prompt = str(job.get("prompt") or "")
|
||||
skills = job.get("skills")
|
||||
|
||||
# Run data-collection script if configured, inject output as context.
|
||||
|
|
@ -933,6 +933,8 @@ def _build_job_prompt(job: dict, prerun_script: Optional[tuple] = None) -> str:
|
|||
if skills is None:
|
||||
legacy = job.get("skill")
|
||||
skills = [legacy] if legacy else []
|
||||
elif isinstance(skills, str):
|
||||
skills = [skills]
|
||||
|
||||
skill_names = [str(name).strip() for name in skills if str(name).strip()]
|
||||
if not skill_names:
|
||||
|
|
@ -1015,7 +1017,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
|||
Tuple of (success, full_output_doc, final_response, error_message)
|
||||
"""
|
||||
job_id = job["id"]
|
||||
job_name = job["name"]
|
||||
job_name = str(job.get("name") or job.get("prompt") or job_id or "cron job")
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# no_agent short-circuit — the script IS the job, no LLM involvement.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue