feat(contracts,db): scheduled_job registry tables and schedules API contracts
- 0028_scheduled_jobs: scheduled_job (stable key, group, kind task|agent_check, enabled/schedule with defaults + customized flag, config, last_* / next_run_at / run_requested_at / running_since, counters) and scheduled_job_run history - contracts/schedules.ts: ScheduleSpec (interval | 5-field cron), AgentCheckConfig (goal template, capability allowlist, budget, trigger condition, allowRecover, reportAsDecision), board / run views - Api.schedules: list, create (agent_check only), update, remove, run-now, skip, runs; all admin + console session only Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f0386af319
commit
f8f70d8e09
5 files changed
+373
No files matched your search
@@ -0,0 +1,73 @@
|
||||
import type { Kysely } from "kysely";
|
||||
import { dropTables, execDdl, oneOf } from "../src/migration-helpers.ts";
|
||||
|
||||
// W7-C 定时任务统一注册表(docs/runbook/schedules.md):
|
||||
// - scheduled_job:稳定 key 为主键(如 feeds.poll);模块启动时登记默认值(customized 后不覆盖 enabled/schedule);
|
||||
// io worker 上持有咨询锁的调度器按 next_run_at / run_requested_at 触发,结果写回 last_* 与计数。
|
||||
// kind=agent_check 为 Agent 巡检(config 存目标模板/能力白名单/预算/触发条件),builtin=false 为用户自定义。
|
||||
// - scheduled_job_run:运行历史,每个任务只保留最近若干条(调度器写入时裁剪);巡检入队的 agent_run 结束后
|
||||
// 调度器回填 agent_status 与 summary。
|
||||
|
||||
const GROUPS = ["collect", "download", "organize", "maintain", "agent"] as const;
|
||||
const KINDS = ["task", "agent_check"] as const;
|
||||
const STATUSES = ["ok", "failed", "skipped", "running"] as const;
|
||||
const TRIGGERS = ["schedule", "manual", "skip"] as const;
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(
|
||||
db,
|
||||
`
|
||||
create table scheduled_job (
|
||||
id text primary key,
|
||||
name text not null,
|
||||
description text,
|
||||
board_group text not null ${oneOf("board_group", GROUPS)},
|
||||
kind text not null ${oneOf("kind", KINDS)},
|
||||
builtin boolean not null default true,
|
||||
available boolean not null default true,
|
||||
enabled boolean not null,
|
||||
default_enabled boolean not null,
|
||||
schedule jsonb not null,
|
||||
default_schedule jsonb not null,
|
||||
customized boolean not null default false,
|
||||
config jsonb not null default '{}'::jsonb,
|
||||
last_run_at timestamptz,
|
||||
last_status text ${oneOf("last_status", STATUSES)},
|
||||
last_duration_ms integer,
|
||||
last_error text,
|
||||
next_run_at timestamptz,
|
||||
run_requested_at timestamptz,
|
||||
running_since timestamptz,
|
||||
run_count integer not null default 0,
|
||||
fail_count integer not null default 0,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
create trigger scheduled_job_touch_updated_at before update on scheduled_job
|
||||
for each row execute function umt_touch_updated_at();
|
||||
create index scheduled_job_due_idx on scheduled_job (next_run_at) where enabled;
|
||||
|
||||
create table scheduled_job_run (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
job_id text not null references scheduled_job (id) on delete cascade,
|
||||
trigger text not null ${oneOf("trigger", TRIGGERS)},
|
||||
status text not null ${oneOf("status", STATUSES)},
|
||||
started_at timestamptz not null default now(),
|
||||
finished_at timestamptz,
|
||||
duration_ms integer,
|
||||
error text,
|
||||
summary text,
|
||||
agent_run_id uuid references agent_run (id) on delete set null,
|
||||
agent_status text,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
create index scheduled_job_run_job_idx on scheduled_job_run (job_id, started_at desc);
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: 同上
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await execDdl(db, dropTables(["scheduled_job_run", "scheduled_job"]));
|
||||
}
|
||||
@@ -202,6 +202,11 @@ export type ResolutionAttemptOutcome =
|
||||
| "manual"
|
||||
| "skipped_locked";
|
||||
export type ResolutionAttemptActorType = "user" | "agent" | "system" | "api_key";
|
||||
export type ScheduledJobBoardGroup = "collect" | "download" | "organize" | "maintain" | "agent";
|
||||
export type ScheduledJobKind = "task" | "agent_check";
|
||||
export type ScheduledJobLastStatus = "ok" | "failed" | "skipped" | "running";
|
||||
export type ScheduledJobRunTrigger = "schedule" | "manual" | "skip";
|
||||
export type ScheduledJobRunStatus = "ok" | "failed" | "skipped" | "running";
|
||||
export type SecretPurpose =
|
||||
| "site_cookie"
|
||||
| "site_passkey"
|
||||
@@ -1097,6 +1102,48 @@ export interface ResolutionAttemptTable {
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface ScheduledJobTable {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
board_group: ScheduledJobBoardGroup;
|
||||
kind: ScheduledJobKind;
|
||||
builtin: Generated<boolean>;
|
||||
available: Generated<boolean>;
|
||||
enabled: boolean;
|
||||
default_enabled: boolean;
|
||||
schedule: Json;
|
||||
default_schedule: Json;
|
||||
customized: Generated<boolean>;
|
||||
config: Generated<Json>;
|
||||
last_run_at: Timestamptz | null;
|
||||
last_status: ScheduledJobLastStatus | null;
|
||||
last_duration_ms: number | null;
|
||||
last_error: string | null;
|
||||
next_run_at: Timestamptz | null;
|
||||
run_requested_at: Timestamptz | null;
|
||||
running_since: Timestamptz | null;
|
||||
run_count: Generated<number>;
|
||||
fail_count: Generated<number>;
|
||||
created_at: GeneratedTimestamptz;
|
||||
updated_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface ScheduledJobRunTable {
|
||||
id: Generated<string>;
|
||||
job_id: string;
|
||||
trigger: ScheduledJobRunTrigger;
|
||||
status: ScheduledJobRunStatus;
|
||||
started_at: GeneratedTimestamptz;
|
||||
finished_at: Timestamptz | null;
|
||||
duration_ms: number | null;
|
||||
error: string | null;
|
||||
summary: string | null;
|
||||
agent_run_id: string | null;
|
||||
agent_status: string | null;
|
||||
created_at: GeneratedTimestamptz;
|
||||
}
|
||||
|
||||
export interface SecretTable {
|
||||
id: Generated<string>;
|
||||
name: string;
|
||||
@@ -1427,6 +1474,8 @@ export interface DB {
|
||||
preference_tag: PreferenceTagTable;
|
||||
reconcile_run: ReconcileRunTable;
|
||||
resolution_attempt: ResolutionAttemptTable;
|
||||
scheduled_job: ScheduledJobTable;
|
||||
scheduled_job_run: ScheduledJobRunTable;
|
||||
secret: SecretTable;
|
||||
server_mapping: ServerMappingTable;
|
||||
settings: SettingsTable;
|
||||
@@ -1589,6 +1638,12 @@ export type ReconcileRunUpdate = Updateable<ReconcileRunTable>;
|
||||
export type ResolutionAttemptRow = Selectable<ResolutionAttemptTable>;
|
||||
export type NewResolutionAttempt = Insertable<ResolutionAttemptTable>;
|
||||
export type ResolutionAttemptUpdate = Updateable<ResolutionAttemptTable>;
|
||||
export type ScheduledJobRow = Selectable<ScheduledJobTable>;
|
||||
export type NewScheduledJob = Insertable<ScheduledJobTable>;
|
||||
export type ScheduledJobUpdate = Updateable<ScheduledJobTable>;
|
||||
export type ScheduledJobRunRow = Selectable<ScheduledJobRunTable>;
|
||||
export type NewScheduledJobRun = Insertable<ScheduledJobRunTable>;
|
||||
export type ScheduledJobRunUpdate = Updateable<ScheduledJobRunTable>;
|
||||
export type SecretRow = Selectable<SecretTable>;
|
||||
export type NewSecret = Insertable<SecretTable>;
|
||||
export type SecretUpdate = Updateable<SecretTable>;
|
||||
|
||||
Reference in new issue
Block a user