Skip to content

feat(desktop): per-job model switcher in the cron detail panel and sidebar context menu - #94652

Open
addelh wants to merge 3 commits into
NousResearch:mainfrom
addelh:feat/desktop-cron-model-switcher
Open

feat(desktop): per-job model switcher in the cron detail panel and sidebar context menu#94652
addelh wants to merge 3 commits into
NousResearch:mainfrom
addelh:feat/desktop-cron-model-switcher

Conversation

@addelh

@addelh addelh commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Surfaces the existing per-job model/provider override in the desktop cron UI — where it was only reachable via the CLI (hermes cron edit <id> --model … --provider …) or by hand-editing jobs.json.

The backend has supported per-job model pins since the beginning (resolution at fire time: per-job pin → cron.model → global default). But the desktop UI never exposed it — meaning users who wanted to route a cheap summarising job to a cheap model and a heavy reasoning job to a powerful model had to drop to the CLI.

What's new

CronJobDetail panel — a model switcher button appears next to the Resume/Trigger action buttons. It opens the same ModelPickerDialog the chat composer uses — so users see all their configured providers and actually-available models, with pricing, search, and the same UX they already know. Selecting a model calls updateCronJob({ model, provider }) immediately; no editor round-trip needed. The button label shows the currently pinned model name, or "Model" when following the default.

Sidebar right-click context menu — a "Change model" entry opens the full manage panel where the switcher lives, keeping model configuration in one discoverable place rather than spreading it across the sidebar.

Script-only jobs — the switcher is hidden for no_agent jobs since they don't run an LLM.

Related Issue

Closes the "model switcher missing from the desktop cron UI" gap. Related: #89513 (Models pane missing cron config), #89562 (fleet model defaults in UI), #93004 (per-job reasoning effort picker — this PR is the model-axis counterpart).

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • apps/desktop/src/app/cron/index.tsxModelPickerDialog in CronJobDetail, onModelChange handler calls updateCronJob with { model, provider }
  • apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx — "Change model" context menu item
  • apps/desktop/src/i18n/{en,ja,zh,zh-hant,ar}.ts + types.tsmodelSwitcher, modelUpdated, changeModel strings
  • apps/desktop/src/app/cron/cron-model-switcher.test.ts — test coverage for the model switcher payload shape

How to Test

  1. cd apps/desktop && npx vitest run src/app/cron/ — 38 pass (incl. new tests)
  2. npx tsc -p tsconfig.json --noEmit — clean
  3. Open the Scheduled Jobs panel → select a job → click the "Model" button next to Resume/Trigger → pick a model → verify the job's model pin updates
  4. Right-click a cron job in the sidebar → "Change model" opens the manage panel with the switcher

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(desktop): …)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this feature
  • I've run the relevant tests and they pass (38/38)
  • I've added tests for my changes
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • N/A — no config keys added (surfaces existing model/provider fields already in CronJobUpdates)
  • N/A — renderer-only change, no cross-platform impact

…debar context menu

Add a model switcher button to the CronJobDetail header (next to Resume/Trigger)
that opens the same ModelPickerDialog used in chat, letting users pick any
configured provider/model for a specific cron job without dropping to the CLI.

The sidebar right-click context menu gains a 'Change model' entry that opens the
full manage panel where the switcher now lives — keeping model configuration in
one discoverable place rather than the CLI-only --model flag.

Backend support already exists (per-job model/provider pins resolved at fire
time: per-job pin > cron.model > global default). This surfaces it in the UI.

Changes:
- apps/desktop/src/app/cron/index.tsx: ModelPickerDialog in CronJobDetail,
  onModelChange handler calls updateCronJob with { model, provider }
- apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx: 'Change model'
  context menu item opens the manage panel
- apps/desktop/src/i18n/{en,ja,zh,zh-hant,ar}.ts: modelSwitcher, modelUpdated,
  changeModel strings
- apps/desktop/src/app/cron/cron-model-switcher.test.ts: test coverage
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) comp/cron Cron scheduler and job management labels Aug 25, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

This PR adds a per-job model switcher to the cron detail panel and sidebar context menu, allowing users to change the model/provider for individual cron jobs without opening the full editor. The implementation reuses the existing ModelPickerDialog component and integrates with the updateCronJob API. The UI is gated behind !jobIsScriptOnly(job) (script-only jobs have no AI model). Internationalization is thorough — all six supported locales (en, ar, ja, zh, zh-hant, and the type definition) are updated with modelSwitcher, modelUpdated, and changeModel strings.

Concern 1 — Sidebar context menu "Change model" opens the manage panel instead of a model picker: At cron-jobs-section.tsx:299, the "change-model" action's onSelect is set to onManage, which presumably opens the detail panel. This means clicking "Change model" in the sidebar context menu doesn't directly open a model picker — it opens the detail panel where the user then has to find and click the model button. This is a reasonable design choice, but the menu label "Change model" sets an expectation of direct action that isn't met. Consider either renaming the sidebar item to "Manage model" or actually opening a model picker directly from the context menu.

Concern 2 — onModelChange doesn't handle the stale return case with user feedback: At index.tsx:705, when stale is true, the function returns early without showing any feedback. The mutateAndRefreshCronJobs return value { stale } presumably indicates the job list was stale (e.g., the job was modified elsewhere). The user gets no notification that their model change was silently dropped. Consider showing a warning notification like "This job was modified elsewhere — refresh and try again" when stale is true.

Concern 3 — Model switcher button shows modelOverride or c.modelSwitcher but not the provider: At index.tsx:839, the button label shows the model override string or the localized "Model" label. If a user has set a provider override (e.g., "anthropic") alongside the model, the button only shows the model name. Showing both (e.g., "claude-sonnet-4 (anthropic)") would give users clearer feedback about the current configuration at a glance. This is a minor UX concern.

Concern 4 — Test file doesn't test the actual onModelChange handler: The test at cron-model-switcher.test.ts tests cronEditorUpdates and CronJobUpdates type shape, but doesn't test the onModelChange callback in index.tsx that calls updateCronJob. The test at line 7-30 essentially asserts that object literal properties equal themselves (updates.model === 'claude-sonnet-4' where updates was just constructed with that value). These are tautological — they test JavaScript object construction, not application logic. The cronEditorUpdates tests at lines 39-93 are more valuable since they test a real function, but the first three test cases add no meaningful coverage.

The modelBusy state correctly disables the button during the API call, preventing double-submission. The error handling in the onSelect callback at index.tsx:878 catches exceptions and shows an error notification, which is good UX.

@addelh

addelh commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed the automated notes against the current head. Concern 4 was actionable: the original first three tests only proved object construction. Fixed in be36262dc by extracting the exact cronModelUpdates() payload builder used by onModelChange and testing pin, clear, whitespace normalization, and empty-provider behavior (RED first, then 38 cron UI tests, Desktop typecheck, changed-file ESLint, and git diff --check all pass).

The other notes do not currently identify behavioral defects: the sidebar action intentionally opens the selected job in the manage panel (the commit description calls out that two-step UX); stale here means the connection/profile request scope changed, not that another writer modified this job, so silently refusing to publish/toast into the new scope is deliberate; and the picker itself displays provider context while the compact action label stays model-first.

@addelh

addelh commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up: the new head is mergeable, Docker/Nix/security/ancestry are green, but the aggregate gate is red because one unrelated UI test timed out: settings/gateway-settings.test.tsx (15s timeout). The cron switcher tests themselves passed in that run. I checked the exact GitHub synthetic merge ref 777269396 locally and the timed-out gateway test passed in isolation (1/1); current main also passed it. This is consistent with a suite-level timing flake, not a changed-path failure. I cannot rerun the repository job with contributor permissions, so the remaining action is a maintainer rerun of CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants