fix(cron): transient run prompt survives the relay-fronted gateway forward

cronjob(action='run', prompt=...) context was silently dropped when the
manual run forwarded to the gateway (#96010 follow-up): POST
/api/jobs/{id}/run took no body. The forward now sends {prompt} in the
request body; the api_server validates it (length cap + strict injection
scan, same as stored prompts) and trigger_job stamps it as a transient
manual_run_prompt alongside manual_run_at. run_one_job consumes the stamp
for that single fire and mark_job_run clears it, so it never persists
into the job definition or later scheduled fires.
This commit is contained in:
Teknium
2026-08-27 20:02:43 -07:00
parent 9c87a7c79e
commit 31579f781e
6 changed files with 250 additions and 13 deletions
+23 -1
View File
@@ -6789,8 +6789,30 @@ class APIServerAdapter(BasePlatformAdapter):
job_id, id_err = self._check_job_id(request)
if id_err:
return id_err
# Optional transient per-run context forwarded from a standalone
# `hermes cron run` / cronjob(action='run', prompt=...) — same length
# cap and strict injection scan as a stored job prompt.
extra_prompt = None
try:
job = _cron_trigger(job_id)
body = await request.json()
except Exception:
body = None
if isinstance(body, dict):
raw_prompt = body.get("prompt")
if raw_prompt is not None:
extra_prompt = str(raw_prompt)
if len(extra_prompt) > self._MAX_PROMPT_LENGTH:
return web.json_response(
{"error": f"Prompt must be ≤ {self._MAX_PROMPT_LENGTH} characters"},
status=400,
)
if extra_prompt and _scan_cron_prompt is not None:
scan_error = _scan_cron_prompt(extra_prompt)
if scan_error:
return web.json_response({"error": scan_error}, status=400)
extra_prompt = extra_prompt or None
try:
job = _cron_trigger(job_id, extra_prompt=extra_prompt)
if not job:
return web.json_response({"error": "Job not found"}, status=404)
return web.json_response({"job": job})