From 34439d18e573997d07242bfbaebd76142b37811e Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:27:54 +0200 Subject: [PATCH] docs(cron): address linked guide and run history review Signed-off-by: w3lld1 <42353747+w3lld1@users.noreply.github.com> --- docs/api-reference.md | 16 ++++++++++++---- docs/cron-guide.md | 4 ++-- docs/wiki/Cron-Jobs.md | 3 +++ docs/wiki/The-Dashboard.md | 2 +- test/cron-docs.test.ts | 15 ++++++++++++++- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/api-reference.md b/docs/api-reference.md index 810c321d..f19cf335 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -120,7 +120,7 @@ are required according to the selected prompt and schedule: | `launchCommand` | Optional single-line string, at most 2000 characters; for shell jobs | | `promptMode` | `inline_text` or `prompt_file_path` | | `promptText` | Required for `inline_text`; nonempty single-line string, at most 100000 characters | -| `promptFilePath` | Required for `prompt_file_path`; regular file confined to `workingDir`, at most 1 MiB, read when the job fires | +| `promptFilePath` | Required for `prompt_file_path`; absolute path inside `workingDir` to a regular file, at most 1 MiB, read when the job fires | | `inputMode` | `paste` or `typed` | | `scheduleType` | `once`, `interval`, `daily`, or `weekly` | | `runAt` | Required for `once`; positive integer Unix timestamp in milliseconds | @@ -138,6 +138,8 @@ When changing `promptMode` or `scheduleType`, supply the fields the new mode nee `Run Now` works even when the job is disabled, bypasses the scheduled concurrency policy, and does not change the schedule. `activeAgents` counts live sessions of the same agent type, excluding sessions created by this job. +For recurring jobs with `autoClosePreviousSession` enabled (the default), `Run Now` +also closes the previous run's session before launching, even if it is still working. ### Job and run response fields @@ -145,6 +147,7 @@ the same agent type, excluding sessions created by this job. `owner` (multi-user mode), `createdAt`, `updatedAt`, `lastRunAt`, `nextRunAt`, `lastStatus`, `lastDueKey`, and optional `completedOnce`. Times are Unix milliseconds; `lastRunAt`, `nextRunAt`, `lastStatus`, and `lastDueKey` can be `null`. +`lastDueKey` is an opaque internal duplicate-launch guard, not a stable API format. `CronJobRun` contains `id`, `cronJobId`, nullable `sessionId` and `sessionName`, `startedAt`, nullable `finishedAt`, `status`, optional `errorMessage`, @@ -153,18 +156,23 @@ Run times are also Unix milliseconds. Status is one of `created`, `session_started`, `prompt_sent`, `failed`, or `skipped`. Prompt delivery continues asynchronously after session launch, so `Run Now` can -return `session_started` before the prompt is sent. Read run history for the later -status. `finishedAt` refers to the launch/prompt-delivery attempt, **not completion +return `session_started` before the prompt is sent. Read run history for subsequent +updates, but do not assume a terminal status will follow: if the session is closed +during the readiness wait or the server restarts before delivery, the run can remain +`session_started` indefinitely with `finishedAt: null`. +`finishedAt` refers to the launch/prompt-delivery attempt, **not completion of the agent's task**; `prompt_sent` does not prove that the task succeeded. In multi-user mode, list/history endpoints filter to accessible jobs. An unknown or inaccessible job returns `NOT_FOUND`. Job creation and updates can return -`FORBIDDEN` for a working directory outside the owner's workspace or a shell / +`403 FORBIDDEN` for a working directory outside the owner's workspace or a shell / launch-command job without the required privilege grant. Invalid definitions or working directories return `INVALID_INPUT`; launch/delivery failures are recorded on the run, so inspect its `status` and `errorMessage` even after an HTTP success. See [Cron Jobs](wiki/Cron-Jobs.md) for the UI, scheduling, and prompt-file rules. +See the [complete cron guide](cron-guide.md) for the `cron:runCreated` and +`cron:runUpdated` SSE events. ## Long-polling (agent wait) diff --git a/docs/cron-guide.md b/docs/cron-guide.md index a865196b..92ba3320 100644 --- a/docs/cron-guide.md +++ b/docs/cron-guide.md @@ -5,7 +5,7 @@ spin up a Claude (or shell / OpenCode / Codex / Antigravity / Gemini / Pi) sessi feed it a prompt. Think "cron for agent sessions": _"every weekday at 3am, open a Claude session in `~/proj` and tell it to update dependencies and open a PR."_ -- **UI**: the **⏰ Cron** button in the header → the Cron Jobs modal (`#cronModal`). +- **UI**: the **⏰ Cron** button in the bottom toolbar → the Cron Jobs modal (`#cronModal`). - **API**: `/api/cron/jobs*` and `/api/cron/runs`. - **Code**: `src/cron/cron-service.ts`, `src/cron/cron-time.ts`, `src/cron/cron-input.ts`, types in `src/types/cron.ts`, routes in `src/web/routes/cron-routes.ts`, @@ -24,7 +24,7 @@ Claude session in `~/proj` and tell it to update dependencies and open a PR."_ ### In the browser -1. Click **⏰ Cron** in the header. +1. Click **⏰ Cron** in the bottom toolbar. 2. Click **+ New Job**. 3. Fill in a **name**, pick an **agent type** and **working directory**, choose a **prompt** (inline text or a file path), pick a **schedule**, and leave diff --git a/docs/wiki/Cron-Jobs.md b/docs/wiki/Cron-Jobs.md index c71c03c4..e45f694f 100644 --- a/docs/wiki/Cron-Jobs.md +++ b/docs/wiki/Cron-Jobs.md @@ -106,6 +106,9 @@ Every fire is recorded per job, with a status: History records whether the session started and the prompt was delivered, **not whether the agent's task succeeded**. `prompt_sent` is not a task-completion signal. +If the session is closed during the readiness wait or the server restarts before +delivery, a run can remain `session_started` indefinitely with `finishedAt: null`; +clients must not poll forever waiting for a terminal status. The schedule is advanced **before** the session launches, so a slow start cannot cause the same job to re-trigger. diff --git a/docs/wiki/The-Dashboard.md b/docs/wiki/The-Dashboard.md index 1d7a3023..27c4a7a7 100644 --- a/docs/wiki/The-Dashboard.md +++ b/docs/wiki/The-Dashboard.md @@ -254,7 +254,7 @@ Two extras depending on the device: | Respawn | Session Options | [Keeping Agents Running](Keeping-Agents-Running) | | Ralph | Session Options | [Autonomous Loops](Autonomous-Loops) | | Orchestrator | Toolbar | [Autonomous Loops](Autonomous-Loops) | -| Cron | Header ⏰ (opt-in) | [Cron Jobs](Cron-Jobs) | +| Cron | Bottom toolbar ⏰ (opt-in) | [Cron Jobs](Cron-Jobs) | | Subagents | Automatic while agents run | [Watching Agents Work](Watching-Agents-Work) | | Ultracode | Header (opt-in) | [Watching Agents Work](Watching-Agents-Work) | | File Viewer | Header | [Working With Files](Working-With-Files) | diff --git a/test/cron-docs.test.ts b/test/cron-docs.test.ts index 8c154805..ce410c93 100644 --- a/test/cron-docs.test.ts +++ b/test/cron-docs.test.ts @@ -1,3 +1,4 @@ +/** @fileoverview Guards cron docs against drift from routes, schema, statuses, and UI entry points. */ import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { CronJobSchema } from '../src/web/schemas.js'; @@ -36,8 +37,20 @@ describe('cron documentation', () => { expect(guide).toContain(`| \`${status}\``); expect(cronSection(reference)).toContain(`\`${status}\``); } - expect(guide).toContain("not whether\nthe agent's task succeeded"); + expect(guide.replace(/\s+/g, ' ')).toContain("not whether the agent's task succeeded"); expect(guide).toContain('bottom toolbar'); expect(guide).toContain('App Settings → Header & Panels → Scheduling'); }); + + it('keeps linked pages consistent and explains non-terminal launch history', () => { + expect(read('docs/wiki/The-Dashboard.md')).toMatch(/\| Cron\s*\| Bottom toolbar/); + expect(read('docs/cron-guide.md')).not.toMatch(/Cron\*\* (?:button )?in the header/); + for (const text of [reference, guide]) { + const normalized = text.replace(/\s+/g, ' '); + expect(normalized).toContain('session is closed during the readiness wait'); + expect(normalized).toContain('server restarts before delivery'); + expect(normalized).toContain('`session_started` indefinitely with `finishedAt: null`'); + } + expect(reference).toContain("also closes the previous run's session before launching"); + }); });