mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
- Reject multi-line prompts end-to-end: schema refines on promptText/ launchCommand, runtime check in resolvePrompt (prompt-file content; trailing newlines tolerated), matching cron-ui form validation — delivery is single-line only, so multi-line was silently corrupted (typed mode fused lines, paste mode submitted partials) - Close the workingDir confinement bypass (arbitrary server-side file read, e.g. workingDir=/proc + /proc/self/environ): realpath-resolve workingDir before the containment check, reject '/' and blocked/pseudo-fs trees (/proc, /sys, /dev + the attachment-guard blocklist) at fire time AND at job create/update (workingDir must exist and be a directory) - Session lifecycle: new per-job autoClosePreviousSession (default true, recurring schedules only; ignored for 'once') — the previous run's still-open session is closed via the normal cleanupSession path when the next run fires; UI switch added; 50-session cap math documented in docs/cron-guide.md §8 - skip_if_same_agent_running: count only live sessions (exclude stopped/error dead tabs), exclude sessions created by this job's own runs (fixes the fire-once-then-skip-forever self-deadlock), and a skipped 'once' job stays armed and retries next tick instead of being consumed; liveness filter mirrored in cron-ui _countActiveAgents - Wire launchCommand (was accepted+documented but dead): shell mode sends it via writeViaMux as the first input line after startShell readiness (single-line, schema-enforced); form field shown for shell agent type - Record delivery failures: a false writeViaMux result now fails the run instead of recording a false 'prompt_sent' - Cap saved jobs at MAX_CRON_JOBS (100) to bound state.json growth - Surface field-specific schema messages (drop parseBody custom errorMessage on cron create/update) - Tests: workingDir create/update validation, /proc bypass regression, single-line enforcement (schema+runtime+trailing-newline tolerance), live/own-session skip filtering, once-skip re-arm, auto-close on/off/once, job-count cap Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
/**
|
||
* @fileoverview Cron Jobs type definitions.
|
||
*
|
||
* NOTE: This is intentionally distinct from the existing `ScheduledRun` concept
|
||
* (see src/web/ports/infra-port.ts), which is a run-now, duration-bounded
|
||
* autonomous loop. A `CronJob` is a SAVED, NAMED job with a recurring
|
||
* schedule (once/interval/daily/weekly), enable/disable, next-run calculation,
|
||
* and a history of `CronJobRun` records. The two do not interact.
|
||
*
|
||
* Persisted to `~/.codeman/state.json` via StateStore (see AppState).
|
||
*/
|
||
|
||
import type { SessionMode } from './session.js';
|
||
|
||
/** How a job's fire times are computed. */
|
||
export type ScheduleType = 'once' | 'interval' | 'daily' | 'weekly';
|
||
|
||
/** Where the prompt text comes from. */
|
||
export type PromptMode = 'inline_text' | 'prompt_file_path';
|
||
|
||
/** How the prompt is delivered into the session. */
|
||
export type InputMode = 'paste' | 'typed';
|
||
|
||
/** Lifecycle status of a single job execution. */
|
||
export type CronJobRunStatus = 'created' | 'session_started' | 'prompt_sent' | 'failed' | 'skipped';
|
||
|
||
/** What triggered a run. */
|
||
export type TriggerType = 'scheduled' | 'manual_run_now';
|
||
|
||
/** What to do for an AUTOMATIC run when sessions of the same agent already exist. */
|
||
export type ConcurrencyPolicy = 'warn_only' | 'skip_if_same_agent_running';
|
||
|
||
/**
|
||
* A saved, named cron job.
|
||
*/
|
||
export interface CronJob {
|
||
id: string;
|
||
name: string;
|
||
/** Reuses Codeman's existing session modes; 'shell' covers Terminal/custom. */
|
||
agentType: SessionMode;
|
||
workingDir: string;
|
||
/** Optional custom launch command (only meaningful for 'shell' mode). */
|
||
launchCommand?: string;
|
||
|
||
promptMode: PromptMode;
|
||
promptText?: string;
|
||
promptFilePath?: string;
|
||
inputMode: InputMode;
|
||
|
||
scheduleType: ScheduleType;
|
||
/** once: absolute epoch-ms fire time. */
|
||
runAt?: number;
|
||
/** interval: minutes between fires. */
|
||
intervalMinutes?: number;
|
||
/** daily: 'HH:MM' (24h, server-local time). */
|
||
dailyTime?: string;
|
||
/** weekly: weekdays 0–6 (0=Sunday). */
|
||
weeklyDays?: number[];
|
||
/** weekly: 'HH:MM' (24h, server-local time). */
|
||
weeklyTime?: string;
|
||
|
||
enabled: boolean;
|
||
notes?: string;
|
||
/** Applies to automatic (scheduled) runs only. Manual Run Now always warns client-side. */
|
||
concurrencyPolicy: ConcurrencyPolicy;
|
||
/**
|
||
* Close the still-open session created by this job's previous run before the
|
||
* next run launches (via the normal session-cleanup path), so unattended
|
||
* recurring jobs don't accumulate tabs until the global session cap.
|
||
* Default true. Ignored for 'once' schedules.
|
||
*/
|
||
autoClosePreviousSession?: boolean;
|
||
|
||
// ── Bookkeeping (server-maintained) ─────────────────────────────────────
|
||
createdAt: number;
|
||
updatedAt: number;
|
||
lastRunAt: number | null;
|
||
nextRunAt: number | null;
|
||
lastStatus: CronJobRunStatus | null;
|
||
/** Duplicate-launch guard: identifies the most recent due-time consumed. */
|
||
lastDueKey: string | null;
|
||
/** True once a 'once' job has fired (it is also disabled). */
|
||
completedOnce?: boolean;
|
||
}
|
||
|
||
/**
|
||
* A single execution of a cron job (history record).
|
||
*/
|
||
export interface CronJobRun {
|
||
id: string;
|
||
cronJobId: string;
|
||
sessionId: string | null;
|
||
sessionName: string | null;
|
||
startedAt: number;
|
||
finishedAt: number | null;
|
||
status: CronJobRunStatus;
|
||
errorMessage?: string;
|
||
triggerType: TriggerType;
|
||
/** Best-effort deep link to the created session in the web UI. */
|
||
createdSessionUrl: string | null;
|
||
}
|