feat(dashboard): per-profile cron toggle UI - #26614
Closed
cypres0099 wants to merge 2 commits into
Closed
Conversation
Adds a profile selector to the dashboard's Skills page so each installed
profile's skills.disabled list can be managed from the same dashboard
daemon. Until now, /api/skills only knew the active profile (whichever
HERMES_HOME the dashboard process was launched under), so toggling skills
for a non-active profile required spinning up a second dashboard daemon
bound to that profile's HERMES_HOME — operationally awkward for users
running multiple profiles (e.g. a default + a worker/specialist profile).
Backend
-------
- Add GET /api/profiles/{name}/skills — list a profile's skills
- Add PUT /api/profiles/{name}/skills/toggle — toggle for one profile
- Add is_active to ProfileInfo so the UI can identify the daemon's
resident profile (the one served by the legacy /api/skills routes).
- Reads/writes go directly against the profile's config.yaml
(skills.disabled). The load_config/save_config helpers are bound to
the process-level HERMES_HOME via get_config_path(), so they can't
be reused for cross-profile mutation without invasive global state
changes.
- v1 omits skills.external_dirs scanning for non-active profiles. The
dropdown targets profile-installed skills; external dirs are still
respected by the gateway at runtime.
Frontend
--------
- SkillsPage gains a Select dropdown next to the enabled-of count
(hidden when there's only one installed profile, so default-only
installs are unchanged).
- Default selection is the dashboard's own profile (is_active, or
is_default for older gateways that don't emit the field).
- Switching profile refetches the skills list from the profile-scoped
endpoint; the active-profile selection still uses the legacy
/api/skills route to stay in sync with the gateway's skill index.
- Toggles route through the appropriate endpoint based on selection.
The legacy /api/skills and /api/skills/toggle routes are untouched and
remain the canonical path for the active profile.
Adds a profile selector to the dashboard's Cron screen so cron jobs in every installed profile can be managed from the same dashboard daemon. Until now, /api/cron/jobs only knew the active profile (whichever HERMES_HOME the dashboard process was launched under), forcing users to SSH and run `hermes --profile <name> cron list` to see what was scheduled on sub-profiles. Mirrors the precedent set by the per-profile skills toggle UI (NousResearch#25116). Backend ------- - Add 8 routes under /api/profiles/{name}/cron/jobs* mirroring the legacy /api/cron/jobs* shape: list, get-single, create, update, pause, resume, trigger, delete. Reads and writes go directly against the profile's cron/jobs.json via new web-layer helpers (_load_profile_jobs / _save_profile_jobs); cron/jobs.py's module-global JOBS_FILE is left untouched. - Trigger requests against a profile whose gateway is not running return 409 Conflict — writing next_run_at=now without a live dispatcher tick is a silent no-op. This is defense in depth; the UI disables the button as the primary UX. - Add gateway_running to ProfileInfo so the frontend can gate the trigger button per row. Reuses the existing _check_gateway_running helper (PID-file plus process-identity verification). - Atomic tempfile+replace writes preserve cron/jobs.py's anti-partial- write semantics so a concurrent dispatcher read never sees a torn file. Per-profile in-process write locks serialise concurrent dashboard requests for the same profile. Frontend -------- - CronPage gains a Profile dropdown (hidden when only one profile is installed). The default "All" view aggregates jobs across every profile via Promise.allSettled; the active profile is rendered with an "(active)" suffix matching the skills-patch convention. - Each row carries the owning profile as a badge in the "All" view; the badge is hidden when filtered to a specific profile. - Trigger button is disabled on rows where the owning profile's gateway is down, with tooltip explaining why. - Create form gains a Profile selector when in "All" view (defaults to the active profile); the selector is hidden when filtered. - Schedule cell renders "Repeat: ∞" (recurring) vs "Repeat: 1/1" (one-shot) so the 'every 4320m' vs '4320m' foot-gun is visible at a glance, matching what `hermes cron list` shows. The legacy /api/cron/jobs* routes are untouched and remain the canonical path for the active profile. Default-to-"All" diverges from the per-profile skills page (which defaults to the active profile) because cron is operations work that benefits from cross-profile visibility, where skills is per-profile config work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Closing this stale broad branch rather than trying to revive it in-place. The current PR is very large, failing attribution/supply-chain/test checks, and predates the profile-scoped dashboard work that later landed on main. If per-profile cron controls are still worth pursuing, the better path is a fresh, small PR from current main that targets only the cron UI/API behavior and references this PR for context. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds a Profile dropdown to the dashboard's Cron screen so jobs in every installed profile can be managed from the same dashboard daemon. Mirrors the precedent set by #25116 for the Skills page.
Until now,
/api/cron/jobsonly knew the active profile (whicheverHERMES_HOMEthe dashboard process was launched under), so operators with multi-profile installs (orchestrator + worker sub-profiles, copywriter / SEO / creative bots, etc.) had to SSH to the host and runhermes --profile <name> cron listto see what was scheduled — easy to miss when a sub-profile's cron quietly stops firing.Two intentional divergences from #25116:
next_run_at = nowis a silent no-op without a live dispatcher tick. Pause / resume / edit / delete / create are filesystem-only and work regardless of gateway state.Related Issue
Depends on #25116 (per-profile skills toggle UI). This PR is a sibling: it imports the
is_active/_active_profile_path_strand per-profile-route patterns introduced there. The diff againstmaincurrently includes #25116's commits because this branch sits on top offeat/dashboard-per-profile-skills. Once #25116 merges, this PR's diff narrows to the cron-specific changes only. Order of merge: #25116 first, then this.(Author-attribution check tracked by #26608.)
Type of Change
Changes Made
Backend (
hermes_cli/web_server.py):/api/profiles/{name}/cron/jobs*mirroring the legacy/api/cron/jobs*shape: list, get-single, create, update, pause, resume, trigger, delete._load_profile_jobs/_save_profile_jobshelpers that read/write<profile_dir>/cron/jobs.jsonatomically (tempfile + replace).cron/jobs.py's module-globalJOBS_FILEand_jobs_file_lockare left untouched — same constraint feat(dashboard): per-profile skills toggle UI #25116 worked around forload_config/save_config._check_gateway_running(profile_dir)and returns 409 Conflict when False (defense in depth; the UI disables the button as primary UX).gateway_running: booltoProfileInforesponse via_profile_to_dictand_fallback_profile_dicts. Reuses the existing_check_gateway_runninghelper fromhermes_cli/profiles.py(PID-file plus process-identity verification).threading.Lockdict-of-locks keyed by profile name) serialises concurrent dashboard writes to the same profile'sjobs.json.Frontend (
web/src/lib/api.ts):getProfileCronJobs,createProfileCronJob, etc.).ProfileInfointerface withgateway_running?: boolean(optional for backward-compat with older gateways, matching theis_active?posture from feat(dashboard): per-profile skills toggle UI #25116).Frontend (
web/src/pages/CronPage.tsx):Allas the first/default option.Promise.allSettled; per-row owning-profile badge with the active profile annotated(active).disabledwhenrow.gateway_running === false, with tooltipGateway not running for profile <name>.Repeat: ∞for recurring jobs andRepeat: 1/1for one-shots — verbatim whathermes cron listshows — so theevery-prefix foot-gun ('4320m'= one-shot,'every 4320m'= recurring) is visible at a glance.Tests (
tests/hermes_cli/test_web_server.py):TestNewEndpointscovering: list/create/get roundtrip; pause/resume/trigger/delete lifecycle; PUT update; multiline-prompt roundtrip (closes thehermes cron edit --promptfoot-gun for the dashboard write path); recurring-vs-one-shot schedule kind; empty-list for new profile; unknown-profile 404; invalid-name 400; trigger-when-dormant 409; job-id-not-found 404 across all routes;gateway_runningconsistency between/api/profilesand the trigger gate.The legacy
/api/cron/jobs*routes are untouched and remain the canonical path for the active profile.How to Test
scripts/run_tests.sh tests/hermes_cli/test_web_server.py::TestNewEndpoints -k cron→ 12 new tests pass;-k gateway_runningcovers the 13th.profiles.length > 1; first option isAll.(active).cd web && npm install && npm run build.Checklist
Code
mainfeat/dashboard-per-profile-skillscommit appears in the diff only because feat(dashboard): per-profile skills toggle UI #25116 is not yet merged)scripts/run_tests.sh tests/hermes_cli/test_web_server.py::TestNewEndpointspassesDocumentation & Housekeeping
cli-config.yaml.examplechange — N/A.env.examplechanges — N/A_save_profile_jobsusestempfile.mkstemp+utils.atomic_replace(same helpercron.jobs.save_jobsuses on every platform);_check_gateway_runningalready cross-platform viagateway.status.get_running_pid;scripts/check-windows-footguns.pyclean