From 3f3092a84e78c680d8f31e15b9e521208edfddd1 Mon Sep 17 00:00:00 2001 From: Michael Lam Date: Mon, 4 May 2026 17:12:15 -0700 Subject: [PATCH] feat: add scheduled job profile selector --- api/profiles.py | 52 ++++ api/routes.py | 145 +++++++++--- docs/pr-media/617/task-profile-badges.png | Bin 0 -> 38509 bytes docs/pr-media/617/task-profile-selector.png | Bin 0 -> 47418 bytes static/i18n.js | 27 +++ static/panels.js | 81 ++++++- static/style.css | 1 + tests/test_issue617_cron_profile_selector.py | 224 ++++++++++++++++++ .../test_scheduled_jobs_profile_isolation.py | 2 +- 9 files changed, 495 insertions(+), 37 deletions(-) create mode 100644 docs/pr-media/617/task-profile-badges.png create mode 100644 docs/pr-media/617/task-profile-selector.png create mode 100644 tests/test_issue617_cron_profile_selector.py diff --git a/api/profiles.py b/api/profiles.py index 11511da3..b56868a1 100644 --- a/api/profiles.py +++ b/api/profiles.py @@ -258,6 +258,24 @@ class cron_profile_context_for_home: _cj.OUTPUT_DIR = _cj.CRON_DIR / 'output' except (ImportError, AttributeError): logger.debug("cron_profile_context_for_home: cron.jobs unavailable") + + # cron.scheduler snapshots _hermes_home at import time and run_job() + # reads config/.env from that module global. Patch it alongside + # cron.jobs so manual WebUI runs actually execute under the selected + # profile, not merely write output metadata there (#617). + self._prev_cs = None + try: + import cron.scheduler as _cs + self._prev_cs = ( + getattr(_cs, '_hermes_home', None), + getattr(_cs, '_LOCK_DIR', None), + getattr(_cs, '_LOCK_FILE', None), + ) + _cs._hermes_home = self._home + _cs._LOCK_DIR = self._home / 'cron' + _cs._LOCK_FILE = _cs._LOCK_DIR / '.tick.lock' + except (ImportError, AttributeError): + logger.debug("cron_profile_context_for_home: cron.scheduler unavailable") except Exception: _cron_env_lock.release() raise @@ -275,6 +293,12 @@ class cron_profile_context_for_home: _cj.HERMES_DIR, _cj.CRON_DIR, _cj.JOBS_FILE, _cj.OUTPUT_DIR = self._prev_cj except (ImportError, AttributeError): pass + if getattr(self, '_prev_cs', None) is not None: + try: + import cron.scheduler as _cs + _cs._hermes_home, _cs._LOCK_DIR, _cs._LOCK_FILE = self._prev_cs + except (ImportError, AttributeError): + pass finally: _cron_env_lock.release() return False @@ -313,6 +337,20 @@ class cron_profile_context: _cj.OUTPUT_DIR = _cj.CRON_DIR / 'output' except (ImportError, AttributeError): logger.debug("cron_profile_context: cron.jobs unavailable; env-var only") + + self._prev_cs = None + try: + import cron.scheduler as _cs + self._prev_cs = ( + getattr(_cs, '_hermes_home', None), + getattr(_cs, '_LOCK_DIR', None), + getattr(_cs, '_LOCK_FILE', None), + ) + _cs._hermes_home = home + _cs._LOCK_DIR = home / 'cron' + _cs._LOCK_FILE = _cs._LOCK_DIR / '.tick.lock' + except (ImportError, AttributeError): + logger.debug("cron_profile_context: cron.scheduler unavailable; env-var only") except Exception: _cron_env_lock.release() raise @@ -333,6 +371,12 @@ class cron_profile_context: _cj.HERMES_DIR, _cj.CRON_DIR, _cj.JOBS_FILE, _cj.OUTPUT_DIR = self._prev_cj except (ImportError, AttributeError): pass + if getattr(self, '_prev_cs', None) is not None: + try: + import cron.scheduler as _cs + _cs._hermes_home, _cs._LOCK_DIR, _cs._LOCK_FILE = self._prev_cs + except (ImportError, AttributeError): + pass finally: _cron_env_lock.release() return False @@ -462,6 +506,14 @@ def _set_hermes_home(home: Path): except (ImportError, AttributeError): logger.debug("Failed to patch cron.jobs module") + try: + import cron.scheduler as _cs + _cs._hermes_home = home + _cs._LOCK_DIR = home / 'cron' + _cs._LOCK_FILE = _cs._LOCK_DIR / '.tick.lock' + except (ImportError, AttributeError): + logger.debug("Failed to patch cron.scheduler module") + def _reload_dotenv(home: Path): """Load .env from the profile dir into os.environ with profile isolation. diff --git a/api/routes.py b/api/routes.py index 592431a1..4737df39 100644 --- a/api/routes.py +++ b/api/routes.py @@ -245,52 +245,119 @@ def _cron_output_content_window(text: str, limit: int = _CRON_OUTPUT_CONTENT_LIM return text[-limit:] -def _run_cron_tracked(job, profile_home=None): + + +def _cron_job_for_api(job: dict) -> dict: + """Return a cron job payload with the #617 optional profile field present. + + Legacy jobs intentionally persist without ``profile`` so they keep the + scheduler's server-default behavior. The API still returns ``profile: None`` + so the UI can label that state explicitly instead of guessing. + """ + payload = dict(job or {}) + payload.setdefault("profile", None) + return payload + + +def _cron_jobs_for_api(jobs) -> list[dict]: + return [_cron_job_for_api(job) for job in (jobs or [])] + + +def _available_cron_profile_names() -> set[str]: + from api.profiles import list_profiles_api + + names = {"default"} + for profile in list_profiles_api(): + try: + name = str(profile.get("name") or "").strip() + except AttributeError: + continue + if name: + names.add(name) + return names + + +def _normalize_cron_profile_value(value) -> str | None: + if value is None: + return None + profile = str(value).strip() + if not profile: + return None + if profile not in _available_cron_profile_names(): + raise ValueError(f"Unknown profile: {profile}") + return profile + + +def _profile_home_for_cron_job(job: dict): + """Resolve the execution profile for a cron job, with graceful fallback. + + A missing/blank profile preserves legacy server-default behavior. If a job + points at a profile that was deleted after save, fall back to the active + server profile and log a warning instead of crashing the Run Now path. + """ + from api.profiles import get_active_hermes_home, get_hermes_home_for_profile + + raw = str((job or {}).get("profile") or "").strip() + if not raw: + return get_active_hermes_home() + if raw not in _available_cron_profile_names(): + logger.warning( + "Cron job %s references missing profile %r; falling back to server default", + (job or {}).get("id", "?"), raw, + ) + return get_active_hermes_home() + return get_hermes_home_for_profile(raw) + + +def _run_cron_tracked(job, profile_home=None, execution_profile_home=None): """Wrapper that tracks running state around cron.scheduler.run_job. - ``profile_home`` pins HERMES_HOME for this worker thread so output files - and run metadata land in the profile that triggered the run, not the - process-global default. Captured at dispatch time because the thread runs - after the HTTP request (and its TLS profile) has already been cleared. + ``profile_home`` is the cron store that owns the job row/output metadata. + ``execution_profile_home`` is the selected per-job profile used to load + agent config/.env while running. When no job profile is selected, both homes + are the same and legacy server-default behavior is preserved. """ from cron.scheduler import run_job # import here — runs inside a worker thread from cron.jobs import mark_job_run, save_job_output job_id = job.get("id", "") + execution_profile_home = execution_profile_home or profile_home - # Pin HERMES_HOME for the duration of this thread using a dedicated - # context manager variant that accepts the profile home directly - # (threads have no TLS, so get_active_hermes_home() can't resolve). - ctx = None - if profile_home is not None: + def _with_cron_home(home, fn): + if home is None: + return fn() from api.profiles import cron_profile_context_for_home - ctx = cron_profile_context_for_home(profile_home) - ctx.__enter__() + with cron_profile_context_for_home(home): + return fn() try: - success, output, final_response, error = run_job(job) - save_job_output(job_id, output) + success, output, final_response, error = _with_cron_home( + execution_profile_home, lambda: run_job(job) + ) - # Match the scheduled cron path: an apparently successful run with no - # final response should not leave the job looking healthy. - if success and not final_response: - success = False - error = "Agent completed but produced empty response (model error, timeout, or misconfiguration)" + # Persist output and run metadata back to the job's owning cron store, + # even when the selected execution profile is different. + def _persist_success(): + save_job_output(job_id, output) - mark_job_run(job_id, success, error) + # Match the scheduled cron path: an apparently successful run with no + # final response should not leave the job looking healthy. + _success, _error = success, error + if _success and not final_response: + _success = False + _error = "Agent completed but produced empty response (model error, timeout, or misconfiguration)" + + mark_job_run(job_id, _success, _error) + + _with_cron_home(profile_home, _persist_success) except Exception as e: logger.exception("Manual cron run failed for job %s", job_id) try: - mark_job_run(job_id, False, str(e)) + _with_cron_home(profile_home, lambda: mark_job_run(job_id, False, str(e))) except Exception: logger.debug("Failed to mark manual cron run failure for %s", job_id) finally: - if ctx is not None: - try: - ctx.__exit__(None, None, None) - except Exception: - logger.debug("Failed to release cron_profile_context for %s", job_id) _mark_cron_done(job_id) _PROVIDER_ALIASES = { @@ -2435,7 +2502,7 @@ def handle_get(handler, parsed) -> bool: from api.profiles import cron_profile_context with cron_profile_context(): - return j(handler, {"jobs": list_jobs(include_disabled=True)}) + return j(handler, {"jobs": _cron_jobs_for_api(list_jobs(include_disabled=True))}) if parsed.path == "/api/crons/output": from api.profiles import cron_profile_context @@ -5552,8 +5619,9 @@ def _handle_cron_create(handler, body): except ValueError as e: return bad(handler, str(e)) try: - from cron.jobs import create_job + from cron.jobs import create_job, update_job + profile = _normalize_cron_profile_value(body.get("profile")) job = create_job( prompt=body["prompt"], schedule=body["schedule"], @@ -5562,7 +5630,9 @@ def _handle_cron_create(handler, body): skills=body.get("skills") or [], model=body.get("model") or None, ) - return j(handler, {"ok": True, "job": job}) + if profile is not None: + job = update_job(job["id"], {"profile": profile}) or job + return j(handler, {"ok": True, "job": _cron_job_for_api(job)}) except Exception as e: return j(handler, {"error": str(e)}, status=400) @@ -5574,11 +5644,21 @@ def _handle_cron_update(handler, body): return bad(handler, str(e)) from cron.jobs import update_job - updates = {k: v for k, v in body.items() if k != "job_id" and v is not None} + try: + updates = {} + for k, v in body.items(): + if k == "job_id": + continue + if k == "profile": + updates[k] = _normalize_cron_profile_value(v) + elif v is not None: + updates[k] = v + except ValueError as e: + return bad(handler, str(e)) job = update_job(body["job_id"], updates) if not job: return bad(handler, "Job not found", 404) - return j(handler, {"ok": True, "job": job}) + return j(handler, {"ok": True, "job": _cron_job_for_api(job)}) def _handle_cron_delete(handler, body): @@ -5624,7 +5704,8 @@ def _handle_cron_run(handler, body): from api.profiles import get_active_hermes_home _profile_home = get_active_hermes_home() - threading.Thread(target=_run_cron_tracked, args=(job, _profile_home), daemon=True).start() + _execution_profile_home = _profile_home_for_cron_job(job) + threading.Thread(target=_run_cron_tracked, args=(job, _profile_home, _execution_profile_home), daemon=True).start() return j(handler, {"ok": True, "job_id": job_id, "status": "running"}) diff --git a/docs/pr-media/617/task-profile-badges.png b/docs/pr-media/617/task-profile-badges.png new file mode 100644 index 0000000000000000000000000000000000000000..ae54288ce02c18514a30e4dbd27917dc05fb38da GIT binary patch literal 38509 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 K&t;ucLK6T8v#PcL literal 0 HcmV?d00001 diff --git a/docs/pr-media/617/task-profile-selector.png b/docs/pr-media/617/task-profile-selector.png new file mode 100644 index 0000000000000000000000000000000000000000..81f067e298ec131ebea4560b2e9318c247aa4808 GIT binary patch literal 47418 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 literal 0 HcmV?d00001 diff --git a/static/i18n.js b/static/i18n.js index 94625d78..8dd04d74 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -894,6 +894,9 @@ const LOCALES = { cron_prompt_label: 'Prompt', cron_deliver_label: 'Deliver output to', cron_deliver_local: 'Local (save output only)', + cron_profile_label: 'Profile', + cron_profile_server_default: 'server default', + cron_profile_server_default_hint: 'Uses the WebUI server default profile at run time. Existing jobs without a profile keep this legacy behavior.', cron_skills_label: 'Skills', cron_skills_placeholder: 'Add skills (optional)…', cron_skills_edit_hint: 'Skill list is not editable after creation.', @@ -1854,6 +1857,9 @@ const LOCALES = { cron_prompt_label: 'プロンプト', cron_deliver_label: '出力先', cron_deliver_local: 'ローカル (出力を保存のみ)', + cron_profile_label: 'プロフィール', + cron_profile_server_default: 'サーバーデフォルト', + cron_profile_server_default_hint: '実行時に WebUI サーバーのデフォルトプロフィールを使用します。プロフィールのない既存ジョブはこの従来の動作を維持します。', cron_skills_label: 'スキル', cron_skills_placeholder: 'スキルを追加 (任意)…', cron_skills_edit_hint: 'スキル一覧は作成後に編集できません。', @@ -2637,6 +2643,9 @@ const LOCALES = { cron_prompt_label: 'Запрос', cron_deliver_label: 'Доставлять вывод', cron_deliver_local: 'Локально (только сохранение)', + cron_profile_label: 'Профиль', + cron_profile_server_default: 'по умолчанию сервера', + cron_profile_server_default_hint: 'Использует профиль WebUI-сервера по умолчанию во время запуска. Существующие задания без профиля сохраняют это поведение.', cron_skills_label: 'Навыки', cron_skills_placeholder: 'Добавить навыки (необязательно)…', cron_skills_edit_hint: 'Список навыков нельзя изменить после создания.', @@ -3520,6 +3529,9 @@ const LOCALES = { cron_prompt_label: 'Prompt', cron_deliver_label: 'Entregar salida a', cron_deliver_local: 'Local (solo guardar salida)', + cron_profile_label: 'Perfil', + cron_profile_server_default: 'predeterminado del servidor', + cron_profile_server_default_hint: 'Usa el perfil predeterminado del servidor WebUI durante la ejecución. Los trabajos existentes sin perfil conservan este comportamiento heredado.', cron_skills_label: 'Habilidades', cron_skills_placeholder: 'Añadir habilidades (opcional)…', cron_skills_edit_hint: 'La lista de habilidades no es editable después de crear.', @@ -4148,6 +4160,9 @@ const LOCALES = { cron_prompt_label: 'Prompt', cron_deliver_label: 'Ausgabe senden an', cron_deliver_local: 'Lokal (nur speichern)', + cron_profile_label: 'Profil', + cron_profile_server_default: 'Serverstandard', + cron_profile_server_default_hint: 'Verwendet zur Laufzeit das Standardprofil des WebUI-Servers. Bestehende Jobs ohne Profil behalten dieses Legacy-Verhalten.', cron_skills_label: 'Fähigkeiten', cron_skills_placeholder: 'Fähigkeiten hinzufügen (optional)…', cron_skills_edit_hint: 'Die Fähigkeitenliste kann nach der Erstellung nicht bearbeitet werden.', @@ -5308,6 +5323,9 @@ const LOCALES = { cron_prompt_label: '提示词', cron_deliver_label: '输出位置', cron_deliver_local: '本地(仅保存输出)', + cron_profile_label: '配置档', + cron_profile_server_default: '服务器默认', + cron_profile_server_default_hint: '运行时使用 WebUI 服务器默认配置档。没有配置档的现有作业会保留此旧行为。', cron_skills_label: '技能', cron_skills_placeholder: '添加技能(可选)…', cron_skills_edit_hint: '创建后无法再编辑技能列表。', @@ -6346,6 +6364,9 @@ const LOCALES = { cron_prompt_label: '提示', cron_deliver_label: '發送至', cron_deliver_local: '僅本地儲存', + cron_profile_label: '設定檔', + cron_profile_server_default: '伺服器預設', + cron_profile_server_default_hint: '執行時使用 WebUI 伺服器預設設定檔。沒有設定檔的既有工作會保留此舊行為。', cron_skills_label: '技能', cron_skills_placeholder: '選用技能(逗號分隔)', cron_skills_edit_hint: '定義要載入的技能', @@ -7146,6 +7167,9 @@ const LOCALES = { cron_prompt_label: 'Prompt', cron_deliver_label: 'Entregar output para', cron_deliver_local: 'Local (salvar output apenas)', + cron_profile_label: 'Perfil', + cron_profile_server_default: 'padrão do servidor', + cron_profile_server_default_hint: 'Usa o perfil padrão do servidor WebUI no momento da execução. Tarefas existentes sem perfil mantêm esse comportamento legado.', cron_deliver_origin: 'Origem (mesmo chat)', cron_deliver_telegram: 'Telegram', cron_deliver_discord: 'Discord', @@ -8081,6 +8105,9 @@ const LOCALES = { cron_prompt_label: 'Prompt', cron_deliver_label: 'Deliver output to', cron_deliver_local: 'Local (save output only)', + cron_profile_label: 'Profile', + cron_profile_server_default: 'server default', + cron_profile_server_default_hint: 'Uses the WebUI server default profile at run time. Existing jobs without a profile keep this legacy behavior.', cron_skills_label: 'Skills', cron_skills_placeholder: 'Add skills (optional)…', cron_skills_edit_hint: 'Skill list is not editable after creation.', diff --git a/static/panels.js b/static/panels.js index 767c4f0e..2ee6760b 100644 --- a/static/panels.js +++ b/static/panels.js @@ -270,6 +270,58 @@ function _cronStatusMeta(job) { }; } + +function _cronProfileName(profile){ + return (profile || '').toString().trim(); +} + +function _cronProfileLabel(profile){ + const name = _cronProfileName(profile); + return name || (t('cron_profile_server_default') || 'server default'); +} + +function _cronProfileTitle(profile){ + const name = _cronProfileName(profile); + if (name) return (t('cron_profile_label') || 'Profile') + ': ' + name; + return t('cron_profile_server_default_hint') || 'Uses the WebUI server default profile at run time'; +} + +async function loadCronProfiles(){ + if (_cronProfilesCache) return _cronProfilesCache; + try { + const data = await api('/api/profiles'); + _cronProfilesCache = Array.isArray(data.profiles) ? data.profiles : []; + } catch(e) { + _cronProfilesCache = []; + } + return _cronProfilesCache; +} + +function _cronProfileOptions(selected){ + const current = _cronProfileName(selected); + const profiles = Array.isArray(_cronProfilesCache) ? _cronProfilesCache : []; + const seen = new Set(['']); + const opts = [``]; + for (const p of profiles) { + const name = _cronProfileName(p && p.name); + if (!name || seen.has(name)) continue; + seen.add(name); + const label = p && p.is_default ? `${name} (${t('default') || 'default'})` : name; + opts.push(``); + } + if (current && !seen.has(current)) { + opts.push(``); + } + return opts.join(''); +} + +function _refreshCronProfileSelect(selected){ + const sel = $('cronFormProfile'); + if (!sel) return; + const keep = selected === undefined ? sel.value : selected; + sel.innerHTML = _cronProfileOptions(keep); +} + function _cronDiagnostics(job) { const fields = { id: job.id, @@ -297,6 +349,7 @@ async function loadCrons(animate) { refreshBtn.disabled = true; } try { + await loadCronProfiles(); const data = await api('/api/crons'); _cronList = data.jobs || []; if (!_cronList.length) { @@ -311,10 +364,13 @@ async function loadCrons(animate) { item.id = 'cron-' + job.id; const status = _cronStatusMeta(job); const isNewRun = _cronNewJobIds.has(String(job.id)); + const profileLabel = _cronProfileLabel(job.profile); + const profileTitle = _cronProfileTitle(job.profile); item.innerHTML = `
${isNewRun ? '' : ''} ${esc(job.name)} + ${esc(profileLabel)} ${esc(status.label)}
`; item.onclick = () => openCronDetail(job.id, item); @@ -349,6 +405,8 @@ function _renderCronDetail(job){ const schedule = job.schedule_display || (job.schedule && job.schedule.expression) || ''; const skills = Array.isArray(job.skills) && job.skills.length ? job.skills.join(', ') : '—'; const deliver = job.deliver || 'local'; + const profileLabel = _cronProfileLabel(job.profile); + const profileTitle = _cronProfileTitle(job.profile); const lastError = job.last_error ? `
${esc(t('error_prefix').replace(/:\s*$/,''))}
${esc(job.last_error)}
` : ''; const attention = status.state === 'needs_attention' || status.state === 'schedule_error'; const croniterHint = job.last_error && /croniter/i.test(job.last_error) @@ -375,6 +433,7 @@ function _renderCronDetail(job){
${esc(t('cron_next'))}
${esc(nextRun)}
${esc(t('cron_last'))}
${esc(lastRun)}
Deliver
${esc(deliver)}
+
${esc(t('cron_profile_label') || 'Profile')}
${esc(profileLabel)}
Skills
${esc(skills)}
${lastError} @@ -557,6 +616,7 @@ function duplicateCurrentCron(){ schedule: job.schedule_display || (job.schedule && job.schedule.expression) || '', prompt: job.prompt || '', deliver: job.deliver || 'local', + profile: job.profile || '', isEdit: false, }); if (!_cronSkillsCache) { @@ -581,6 +641,7 @@ async function deleteCurrentCron(){ let _cronSelectedSkills=[]; let _cronIsDuplicate = false; let _cronSkillsCache=null; +let _cronProfilesCache=null; function openCronCreate(){ if (typeof switchPanel === 'function' && _currentPanel !== 'tasks') switchPanel('tasks'); @@ -589,9 +650,10 @@ function openCronCreate(){ _cronMode = 'create'; _cronIsDuplicate = false; _cronSelectedSkills = []; - _renderCronForm({ name:'', schedule:'', prompt:'', deliver:'local', isEdit:false }); + _renderCronForm({ name:'', schedule:'', prompt:'', deliver:'local', profile:'', isEdit:false }); _cronSkillsCache = null; api('/api/skills').then(d=>{_cronSkillsCache=d.skills||[]; _bindCronSkillPicker();}).catch(()=>{}); + loadCronProfiles().then(()=>_refreshCronProfileSelect('')).catch(()=>{}); } function openCronEdit(job){ @@ -605,6 +667,7 @@ function openCronEdit(job){ schedule: job.schedule_display || (job.schedule && job.schedule.expression) || '', prompt: job.prompt || '', deliver: job.deliver || 'local', + profile: job.profile || '', isEdit: true, }); if (!_cronSkillsCache) { @@ -612,9 +675,10 @@ function openCronEdit(job){ } else { _bindCronSkillPicker(); } + loadCronProfiles().then(()=>_refreshCronProfileSelect(job.profile || '')).catch(()=>{}); } -function _renderCronForm({ name, schedule, prompt, deliver, isEdit }){ +function _renderCronForm({ name, schedule, prompt, deliver, profile, isEdit }){ const title = $('taskDetailTitle'); const body = $('taskDetailBody'); const empty = $('taskDetailEmpty'); @@ -645,6 +709,13 @@ function _renderCronForm({ name, schedule, prompt, deliver, isEdit }){ ${deliverOpt('telegram','Telegram')} +
+ + +
${esc(t('cron_profile_server_default_hint') || 'Uses the WebUI server default profile at run time')}
+
@@ -729,18 +800,20 @@ async function saveCronForm(){ const schEl=$('cronFormSchedule'); const promptEl=$('cronFormPrompt'); const delivEl=$('cronFormDeliver'); + const profileEl=$('cronFormProfile'); const errEl=$('cronFormError'); if(!schEl||!promptEl||!errEl) return; const name=(nameEl?nameEl.value:'').trim(); const schedule=schEl.value.trim(); const prompt=promptEl.value.trim(); const deliver=delivEl?delivEl.value:'local'; + const profile=profileEl?profileEl.value:''; errEl.style.display='none'; if(!schedule){errEl.textContent=t('cron_schedule_required_example');errEl.style.display='';return;} if(!prompt){errEl.textContent=t('cron_prompt_required');errEl.style.display='';return;} try{ if (_editingCronId) { - const updates = {job_id: _editingCronId, schedule, prompt}; + const updates = {job_id: _editingCronId, schedule, prompt, profile: profile}; if (name) updates.name = name; await api('/api/crons/update', {method:'POST', body: JSON.stringify(updates)}); const editedId = _editingCronId; @@ -752,7 +825,7 @@ async function saveCronForm(){ if (job) openCronDetail(editedId); return; } - const body={schedule,prompt,deliver}; + const body={schedule,prompt,deliver,profile: profile}; if(_cronIsDuplicate) body.enabled=false; if(name)body.name=name; if(_cronSelectedSkills.length)body.skills=_cronSelectedSkills; diff --git a/static/style.css b/static/style.css index fe5cbcee..5fc9bbd6 100644 --- a/static/style.css +++ b/static/style.css @@ -645,6 +645,7 @@ .cron-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;} .cron-name{flex:1;font-size:13px;color:var(--text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .cron-status{font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;flex-shrink:0;} + .cron-profile-badge{font-size:10px;font-weight:650;padding:2px 7px;border-radius:99px;flex-shrink:0;max-width:92px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--border);color:var(--muted);background:var(--surface-subtle);} .cron-status.active{background:rgba(34,197,94,.15);color:var(--success);} .cron-status.paused{background:var(--accent-bg-strong);color:var(--accent-text);} .cron-status.disabled{background:rgba(255,255,255,.07);color:var(--muted);} diff --git a/tests/test_issue617_cron_profile_selector.py b/tests/test_issue617_cron_profile_selector.py new file mode 100644 index 00000000..4d6e9643 --- /dev/null +++ b/tests/test_issue617_cron_profile_selector.py @@ -0,0 +1,224 @@ +"""Regression coverage for issue #617 scheduled-job profile selection.""" + +import io +import json +import sys +import types +from pathlib import Path + +import pytest + +REPO = Path(__file__).resolve().parent.parent + + +class _JSONHandler: + def __init__(self): + self.status = None + self.headers = {} + self.response_headers = [] + self.wfile = io.BytesIO() + + def send_response(self, status): + self.status = status + + def send_header(self, key, value): + self.response_headers.append((key, value)) + + def end_headers(self): + pass + + +def _payload(handler): + return json.loads(handler.wfile.getvalue().decode("utf-8")) + + +def test_cron_api_serializes_legacy_profile_as_explicit_server_default(): + from api.routes import _cron_job_for_api + + legacy = {"id": "legacy", "name": "Legacy job"} + payload = _cron_job_for_api(legacy) + + assert payload["profile"] is None + assert "profile" not in legacy, "API serialization must not mutate stored legacy jobs" + + +def test_cron_profile_value_validates_against_existing_profiles(monkeypatch): + import api.profiles as profiles + from api.routes import _normalize_cron_profile_value + + monkeypatch.setattr( + profiles, + "list_profiles_api", + lambda: [ + {"name": "default"}, + {"name": "research"}, + ], + ) + + assert _normalize_cron_profile_value(" research ") == "research" + assert _normalize_cron_profile_value("") is None + assert _normalize_cron_profile_value(None) is None + with pytest.raises(ValueError, match="Unknown profile: missing"): + _normalize_cron_profile_value("missing") + + +def test_cron_create_api_persists_profile_and_returns_it(monkeypatch): + import api.profiles as profiles + import api.routes as routes + + created = { + "id": "job617", + "name": "Profiled job", + "prompt": "ping", + "schedule": {"kind": "interval", "minutes": 60}, + } + updated = {**created, "profile": "research"} + calls = [] + + cron_pkg = types.ModuleType("cron") + cron_pkg.__path__ = [] + cron_jobs = types.ModuleType("cron.jobs") + cron_jobs.create_job = lambda **kwargs: calls.append(("create", kwargs)) or dict(created) + cron_jobs.update_job = lambda job_id, updates: calls.append(("update", job_id, updates)) or dict(updated) + + monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}]) + monkeypatch.setitem(sys.modules, "cron", cron_pkg) + monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs) + + handler = _JSONHandler() + routes._handle_cron_create( + handler, + { + "name": "Profiled job", + "prompt": "ping", + "schedule": "every 60m", + "deliver": "local", + "profile": "research", + }, + ) + + body = _payload(handler) + assert handler.status == 200 + assert body["ok"] is True + assert body["job"]["profile"] == "research" + assert calls[0][0] == "create" + assert calls[1] == ("update", "job617", {"profile": "research"}) + + +def test_cron_create_api_rejects_unknown_profile_before_persisting(monkeypatch): + import api.profiles as profiles + import api.routes as routes + + cron_pkg = types.ModuleType("cron") + cron_pkg.__path__ = [] + cron_jobs = types.ModuleType("cron.jobs") + cron_jobs.create_job = lambda **kwargs: pytest.fail("invalid profiles must not create jobs") + cron_jobs.update_job = lambda *args, **kwargs: pytest.fail("invalid profiles must not update jobs") + + monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}]) + monkeypatch.setitem(sys.modules, "cron", cron_pkg) + monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs) + + handler = _JSONHandler() + routes._handle_cron_create( + handler, + {"prompt": "ping", "schedule": "every 60m", "profile": "missing"}, + ) + + assert handler.status == 400 + assert "Unknown profile: missing" in _payload(handler)["error"] + + +def test_cron_update_api_accepts_profile_clear_and_rejects_unknown(monkeypatch): + import api.profiles as profiles + import api.routes as routes + + calls = [] + cron_pkg = types.ModuleType("cron") + cron_pkg.__path__ = [] + cron_jobs = types.ModuleType("cron.jobs") + + def update_job(job_id, updates): + calls.append((job_id, updates)) + return {"id": job_id, "name": "Updated", **updates} + + cron_jobs.update_job = update_job + monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}]) + monkeypatch.setitem(sys.modules, "cron", cron_pkg) + monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs) + + handler = _JSONHandler() + routes._handle_cron_update(handler, {"job_id": "job617", "profile": ""}) + assert handler.status == 200 + assert _payload(handler)["job"]["profile"] is None + assert calls == [("job617", {"profile": None})] + + bad_handler = _JSONHandler() + routes._handle_cron_update(bad_handler, {"job_id": "job617", "profile": "ghost"}) + assert bad_handler.status == 400 + assert "Unknown profile: ghost" in _payload(bad_handler)["error"] + assert calls == [("job617", {"profile": None})] + + +def test_manual_cron_run_uses_execution_profile_but_persists_to_owning_store(monkeypatch): + import api.profiles as profiles + import api.routes as routes + + events = [] + + class Ctx: + def __init__(self, home): + self.home = str(home) + + def __enter__(self): + events.append(("enter", self.home)) + + def __exit__(self, exc_type, exc, tb): + events.append(("exit", self.home)) + + cron_pkg = types.ModuleType("cron") + cron_pkg.__path__ = [] + cron_jobs = types.ModuleType("cron.jobs") + cron_jobs.save_job_output = lambda job_id, output: events.append(("save", job_id, output)) + cron_jobs.mark_job_run = lambda job_id, success, error=None: events.append(("mark", job_id, success, error)) + cron_scheduler = types.ModuleType("cron.scheduler") + cron_scheduler.run_job = lambda job: events.append(("run", job["id"])) or (True, "output", "final", None) + + monkeypatch.setattr(profiles, "cron_profile_context_for_home", Ctx) + monkeypatch.setitem(sys.modules, "cron", cron_pkg) + monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs) + monkeypatch.setitem(sys.modules, "cron.scheduler", cron_scheduler) + + routes._mark_cron_running("job617") + routes._run_cron_tracked( + {"id": "job617"}, + profile_home="/hermes/default", + execution_profile_home="/hermes/profiles/research", + ) + + assert events == [ + ("enter", "/hermes/profiles/research"), + ("run", "job617"), + ("exit", "/hermes/profiles/research"), + ("enter", "/hermes/default"), + ("save", "job617", "output"), + ("mark", "job617", True, None), + ("exit", "/hermes/default"), + ] + assert routes._is_cron_running("job617") == (False, 0.0) + + +def test_cron_profile_selector_source_hooks_present(): + panels = (REPO / "static" / "panels.js").read_text(encoding="utf-8") + css = (REPO / "static" / "style.css").read_text(encoding="utf-8") + i18n = (REPO / "static" / "i18n.js").read_text(encoding="utf-8") + + assert "async function loadCronProfiles()" in panels + assert "api('/api/profiles')" in panels + assert "id=\"cronFormProfile\"" in panels + assert "profile: profile" in panels + assert "job.profile" in panels + assert "cron-profile-badge" in panels + assert ".cron-profile-badge" in css + assert "cron_profile_server_default" in i18n + assert "cron_profile_server_default_hint" in i18n diff --git a/tests/test_scheduled_jobs_profile_isolation.py b/tests/test_scheduled_jobs_profile_isolation.py index ec765c81..0d05cc4d 100644 --- a/tests/test_scheduled_jobs_profile_isolation.py +++ b/tests/test_scheduled_jobs_profile_isolation.py @@ -215,7 +215,7 @@ def test_cron_worker_does_not_silently_fall_back_on_profile_context_failure(): from pathlib import Path src = (Path(__file__).resolve().parent.parent / "api" / "routes.py").read_text(encoding="utf-8") - idx = src.find("def _run_cron_tracked(job, profile_home=None):") + idx = src.find("def _run_cron_tracked(job") assert idx != -1, "_run_cron_tracked not found" body = src[idx : idx + 2000]