Skip to content

Add an on-demand cadence to app custom scheduled jobs #6375

Description

@atomantic

Problem / Goal

server/services/autonomousJobs/ (app custom scheduled tasks) offers recurring intervals only. Quota Burn needs to invoke a custom task on demand without it also firing on a clock, and users need to author "run only when I ask" custom tasks. Today the only way to fake this is to create a weekly job and disable it — which also makes it un-runnable.

Add an explicit on-demand cadence as a first-class option across schema, CRUD, due/next-due calculation, timer registration, and the UI.

Note this enum is the autonomous-job interval vocabulary (hourly | every-2-hours | every-4-hours | every-8-hours | daily | weekly | biweekly | monthly | custom). It is a separate vocabulary from the scheduled-CoS-task INTERVAL_TYPES = { ON_DEMAND, CRON } in server/services/taskScheduleConstants.js, and nothing converts between them — do not merge them here.

Scope

server/services/autonomousJobs/constants.js — add the cadence to INTERVAL_OPTIONS and make resolveIntervalMs(interval, customMs) (line 31) return an explicit no-interval sentinel (null) for it. Today the switch ends in default: return DAY, so an unrecognized or on-demand value silently becomes a daily job. That fall-through is the core bug this issue must close.

Every resolveIntervalMs consumer must handle the sentinel explicitly — arithmetic on null yields NaN / Invalid Date:

  • server/services/autonomousJobs/store.js:190 (migrateScriptsState)
  • server/services/autonomousJobs/crud.js:95 (createJob) and :169 (updateJob)
  • client/src/utils/cronHelpers.jsJOB_INTERVAL_OPTIONS (line 287) is the hand-maintained client mirror of the server list and must gain the same row

Due / next-due — there are three separate implementations, all of which need the sentinel:

  • server/services/autonomousJobs/scheduler.js#getDueJobs (line 36) and #computeNextJobRun (line 108)
  • server/services/cosJobScheduler.js#computeNextJobFireTime (line 52) — this is the one that actually arms the timer via registerSingleJobSchedule / registerJobSchedules, not scheduler.js. An on-demand job must register no timer while staying enabled.

ValidationcreateCosJobSchema (server/lib/cosValidation.js:406) declares interval: z.string().optional(), a bare free-form string, so a typo'd cadence already reaches disk and silently reschedules daily. Tighten it to the cadence enum (including the new value) so the boundary rejects it; updateCosJobSchema (:464) inherits via .partial(). intervalMs must not be required or back-filled for the on-demand cadence.

UIclient/src/components/apps/tabs/CustomTasksSection.jsx owns the form state and toPayload, but the cadence widgets are ScheduleFields, exported from client/src/components/cos/JobCard.jsx (line 91; the <select> bound to data.interval is at 133, cadence label at 376, due computation at 160 and 330). Offer the cadence, hide/disable the interval-only inputs when it is selected, and render next-run as "On demand" rather than a date. GET /api/cos/jobs/intervals (server/routes/cosJobRoutes.js) serves INTERVAL_OPTIONS verbatim, so the new row flows through automatically.

Preserve existing recurring cadences and job:spawned bookkeeping exactly. Do not reintroduce a per-app quota-burn scheduled task type.

Acceptance criteria

  • An enabled on-demand custom job is never due across save, reload, server restart, and arbitrary elapsed time — proven by a test that advances fake time well past a day against all three due/next-fire implementations.
  • resolveIntervalMs returns the explicit no-interval sentinel for the on-demand cadence and never falls through to DAY; a test asserts the sentinel is neither DAY nor NaN.
  • cosJobScheduler.registerSingleJobSchedule arms no timer for an on-demand job, and next-due surfaces render no NaN / Invalid Date.
  • createCosJobSchema / updateCosJobSchema reject an unknown cadence string (closing the existing silent-daily typo hole) and accept the new value without intervalMs.
  • An on-demand job remains manually runnable via POST /api/cos/jobs/:id/trigger with unchanged approval/lifecycle rules.
  • Existing recurring jobs keep their cadence, intervalMs, and next-due values — a round-trip test over every INTERVAL_OPTIONS value plus custom.
  • INTERVAL_OPTIONS and JOB_INTERVAL_OPTIONS stay in lockstep, with a test that fails when they drift.
  • Client test covers selecting the cadence in ScheduleFields, the hidden interval inputs, and the "On demand" next-run label.

Notes

Independent of the rest of #6372 and safe to ship first. Children that reference custom tasks (the migration and the picker UI) depend on this cadence existing.


Part of #6372

When this ships, tick its box in the ## Decomposed into checklist on #6372. If it is the last unchecked box, close #6372.

Activity

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

Metadata

Metadata

Assignees

Labels

area:cos-agentsChief-of-Staff autonomous agentsarea:uiUI components and stylingeffort:mediumEffort: mediumenhancementNew feature or requestmodel:mediumModel size: mediumplanTracked by /do:replanplanner:opus-5Plan authored by the opus-5 model

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions