mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49:42 +02:00
Threads per-user ownership through sessions, cases, cron, and the permission policy. All scoping is a no-op in single-user mode (isMultiUserMode() guards). Sessions - Session.owner stamped at every create path from req.authUser / job.owner: POST /api/sessions, /api/run, /api/quick-start, ralph start, cron launch, plan generation. Round-trips through recovery (MuxSession.owner mirror, read muxSession.owner ?? savedState?.owner) and the mux layer. - findSessionOrFail(ctx, id, req) now does a NOT_FOUND owner check (never 403, so other users' session existence is not leaked); wired at ~50 call sites. - List endpoints filtered by owner: GET /api/sessions, /api/sessions/unified (live+persisted+lifecycle scoped, host-wide transcripts admin-only), cron jobs. Permission policy (section 6.3) - resolveClaudeModeForUsername wraps getClaudeModeConfig at every spawn site so a non-granted user is forced to --permission-mode auto (bypass -> auto), including recovery (or a reboot would un-downgrade). buildPromptArgs now respects the session's claudeMode, closing the one-shot (runPrompt) bypass hole. - Shell mode and cron launchCommand require canBypassPermissions: 403 at POST /api/sessions, /api/quick-start create, cron job create, AND cron fire time (re-checked against the owner's current grant). Cases - resolveCasesDir(user): per-user ~/codeman-users/<name>/cases in multi-user, the shared ~/codeman-cases otherwise. All case CRUD + ralph + plan + quick-start resolve through it. resolveCasePath is owner-aware. - GET /api/cases scoped per user (own folders; legacy linked cases admin-only; remote/docker cases owner-filtered). RemoteCase/DockerCase gain owner, stamped at link/quickcreate/import. - Remote + Docker host CRUD is admin-only. - Non-admin workingDir confinement (the linchpin): realpath must resolve inside the user's space, enforced at POST /api/sessions and /api/run BEFORE any disk write. Limits - sessionCapacityState / sessionCapacityMessage centralize the global + per-user cap (CODEMAN_MAX_SESSIONS_PER_USER, default global/2), replacing the 6 copy-pasted MAX_CONCURRENT_SESSIONS checks. Tests: test/ownership-scoping.test.ts (case isolation, host-CRUD gate, workingDir + shell gates, and the scoping helpers). Deferred to phase 4: WS owner gate, SSE fan-out filtering, file-route preview/thumbnail helper scoping, push routing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
104 lines
3.5 KiB
TypeScript
104 lines
3.5 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;
|
||
/** Owning username in multi-user mode; the job launches as this user. Undefined in single-user. */
|
||
owner?: 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;
|
||
}
|