NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · cronjob implementation
Developer reference

cronjob implementation

UI invalidation

Maintainer note: creating, saving or deleting a job refreshes the jobs list, calendar, run history and host conversation links through the plugin's useInvalidateJobs hook. Run now from a job drawer, calendar card or failed-run result uses the same hook after the server accepts the request. The drawer owns its refresh, including an already-queued response; its callers do not repeat it. Calendar state labels and the History and result badges share web-src/runStatus.ts; calendar dots keep their own CSS tones. Detailed run receipts are retained for seven days, daily aggregates for ninety days, and result previews for up to 2,000 characters. These journal limits are fixed; the journal's optional clock is used by tests.

Browser settings contract

The job rail and creation dialogs remain available from both the Automation page and the plugin settings deck. They share ScheduleField, ActiveHoursField and the existing conversation/destination adapters. Simple schedule, interval-unit, active-hours, owner, preset and suggested-time choices use shared dropdowns; the calendar's day/week navigation remains segmented. Presets fill an editable draft rather than binding a job to a template, and custom times remain visible. For one-shot creation, daily and weekly presets may fill a blank time through the existing schedule parser; interval presets leave the time for the user to choose.

Forms use the host's SettingsDocument, SettingsGroup and SettingsRow. Schedule and Advanced groups start closed and persist through the host's folding mechanism. Saved-job keys are cronjob:job:<id>:schedule and cronjob:job:<id>:advanced; creation keys are cronjob:create:<lifecycle>:schedule and cronjob:create:<lifecycle>:advanced. Both entry points share the same saved-job keys. Untitled leading fields and read-only run information do not become disclosures. Groups stay mounted when closed, so folding does not reset a draft or change autosave.

Browser UI API 51 is required for Input.unit. Interval amounts display min or h, and active-hour clock values display h, using the host's shared numeric band. Schema millisecond limits display ms and the shortest interval displays min, without a divisor or storage conversion. Count meaning belongs in the localized label. Native input names, bounds and steps remain intact. Advanced cron validation stays on the server; unrepresented schedules and stored active-hours expressions remain unchanged until edited. Job edits retain revision-checked debounced autosave; creation retains explicit idempotent submission.

See the Scheduling Plugin page in the Elowen user manual.

Server module ownership

index.mjs composes the existing registrations in their original order. It opens the three atomic JSON stores and the SQL run journal, registers account cleanup/reconciliation, connects the concrete readers and writers below, then registers the platform adapter and shipped skills.

  • lib/scheduler.mjs owns CronAdapter, guard execution, due/manual claims, relay, owner alerts and leased pending deliveries. Its jobRunLocation also supplies calendar/navigation/retention reads. The host still injects the live timezone and owner-permission readers; these required inputs have no constructor defaults. The adapter keeps one sequential tick per generation and stops future work at teardown.
  • lib/stores.mjs owns jobs, creation receipts and pending-delivery persistence, strict write reads, model-pair validation and the draft/creation rules shared by HTTP and tools. Writes remain synchronous atomic JSON operations; the existing row-before-receipt crash window and delivery leases are unchanged. lib/runJournal.mjs remains the SQL authority for claims, run receipts and retention.
  • lib/access.mjs owns account grants, owner limits, explicit tool scope and the actor visibility rule. Each execution rechecks permissions. Missing permission reads retain their existing fail-closed behavior.
  • lib/filing.mjs owns immutable conversation associations, metadata-only conversation discovery and the unchanged cron control methods, retainedSessionIds and conversationLinks. Filing never changes execution or delivery. Missing and unavailable associations remain distinct.
  • lib/routes.mjs registers the existing jobs GET/PUT/POST/DELETE routes. Authorization still precedes conflict payloads, and the final strict read follows awaited project authorization before a synchronous save.
  • lib/tools.mjs registers the same five tools through the shared access, filing and write readers.
  • lib/calendar.mjs owns job-response enrichment, the live engine-input reader and week/history/preview routes. Timezone, generation time, tick and lookback are read at request time; intentional preview overrides remain at their callers. Empty results and unreadable jobs still have separate responses.
  • schedule.mjs owns parsing, due/slot decisions, wall-clock resolution, labels and schedule defaults. projection.mjs owns bounded forward expansion and arithmetic day summaries, importing that same parser, cron-day predicate, DST identity and active-hours gate. Projection is pure and performs no storage or network work; its existing candidate and day-slot caps remain unchanged.
  • lib/constants.mjs holds only the shared id generator and JSON response constructor.

For example, a job-list GET filters with the access reader, enriches filing and next-occurrence data through the calendar reader, and projects from the same schedule authority used by the next scheduler tick. No additional host seam or public control/route contract is introduced.

Shared time-zone helpers

The server schedule engine and the scheduler import zonedParts, zonedTimeToMs and WEEKDAYS from elowen-plugin-shared/zonedTime. The shared module owns the formatter cache, local wall-clock fields, UTC conversion and Sunday-first weekday order. No local copies or re-exports remain in schedule.mjs. The cron parser, slot identity, due logic and previews stay in this plugin and use those helpers. This keeps the same time-zone and DST behavior while letting meeting-confirm use the same conversion.

The helpers do no network or storage work. An invalid zone retains the existing machine-zone behavior; schedules, catch-up and duplicate DST slots are unchanged. The plugin requires the core release shipping the helper; shared API version 8 is unchanged. The minimum core version is declared in the current manifest and registry. An older core cannot load this plugin.

Developer reference

History retention and daily totals

GET /plugins/cronjob/api/runs?view=daily&from=2026-09-01&to=2026-09-30 returns { dailySummaries, total }. Each summary contains jobId, jobName, captured ownerUserId, localDate, timezone, ok, error, skipped and total. It uses the same actor visibility and owner/date/job/search/outcome filters as run history, but searches only retained job names and uses limit/offset pagination, capped at 100 rows. An outcome filter selects summaries containing that outcome; all counters remain the day's totals. Without view=daily the existing detailed run list and cursor contract remain. Empty retained history returns an empty summary list. HistoryTab requests both views with independent pagers and never offers run, transcript or result actions for summary rows. Detailed terminal runs older than seven days become summaries retained for 90 days. countsByDay adds both stores without counting pruned details twice.

Captured aggregate ownership

Plugin migration 4 preserves every existing daily-row field and replaces uniqueness by job, local date and captured owner, treating NULL as the instance bucket. Pruning groups by the captured owner and never overwrites another owner's counters on a transfer. Migration applies through the host plugin DB migrator at load; it never needs a live-DB maintenance script. Historical rows retain the ownership recorded by schema 3; prior overwritten attribution cannot be reconstructed from aggregates. HistoryTab is the reader.

Shared live schedule inputs and preview refusal

createJobProjection reads timezone and generation time plus the active CronAdapter's tickMs and cronLookbackMs. Historical week cards override the reference time to just before their local day; draft previews otherwise use the same live policy as the scheduler. POST schedule-preview retains count and fromLocalDate. If planOccurrences exhausts its 100000-candidate budget, the route returns HTTP 422, code preview_budget_exhausted and no occurrences. Invalid grammar remains valid:false with its existing response contract. The advanced editor displays the host API error; valid ordinary modes keep their existing controls. No browser cron expansion is introduced.

Scoped command execution

Cronjob's projectCheck calls the live Sandbox control's runCommand with leaseKind cron, the existing projectRef and explicit account/root actor, the guard timeout/signal and a combined 1 MiB output limit. Core owns capture, cancellation, heartbeat, sanitization and lease release. A missing Sandbox control fails the guard; nonzero or signal exits remain failed checks and producer errors, including cleanup failures, remain errors. Host target and owning-account access are checked at authorization and again before the guard. The scheduler is the consumer.

Shared filing, local times and grammar

CronAdd and HTTP recurring creation use createJobWrites.buildRecurringJob and the existing associationEdit filing authority. A tool supplies its current project binding explicitly even for an instance job; HTTP instance creation remains unbound. Personal creation preserves its existing binding. Direct-delivery origins are supplied only by the tool path and remain independent of filing. createJobWrites.localRunAt is the one create/edit mapper for local one-shots, preserving spring-gap errors and earlier/later instants. ACTIVE_HOURS_PATTERN in scheduleGrammar is shared by daemon active-hour validation and the browser builder; historical malformed-hour handling is unchanged.

Public response cuts

Run-history rows no longer expose requestId or initiatorUserId; operation views and persisted audit/dedupe columns keep them. Week responses omit generatedAt, nowLocalTime, precisionMs and truncated; scheduler exposes only ready. Interval rows retain jobId, schedule, enabled, nextLocalDate and nextLocalTime, removing nextExpectedAt, remainingToday, lastOutcome and lastRunAt. Detailed nextOccurrence keeps expectedAt. planOccurrences returns occurrences/truncated while candidate counting stays private. summarizeJobDay returns exact remaining/head without kind/truncated, bounded to at most 1440 fixed wall clocks and a short head. resolveLocalDateTime returns ms or its existing error; later disambiguation remains. The unused isDue wrapper is removed; dueSlot is the scheduler authority.

Host browser and operation seams

Cronjob uses ElowenUiRuntime.api directly with the published JSON option for create, save, run and preview requests, and its published utils.interpolate type for all existing interpolated consumers. No local serializer or fallback is added. Modal callers no longer pass intent. useRunCronJob returns Promise, retaining accepted-id validation, dock opening, toast and invalidation. Dock presentation uses the existing skipReason and runSkipped translation for terminal skipped operations; no outcome wire field is added. Without skipReason the existing status label remains. Manual HTTP initiators use operationInitiatorFromCredentialScope from the shared package: full/impersonation map to ui, api/advisor to system, agent to agent.