Add event-driven tasks via Gitea webhooks
Webhook endpoint at /api/webhooks/gitea receives Gitea status events,
matches them against configurable event triggers with conditions
(event type, repo glob, state, context), renders task templates with
{{variable}} substitution, and creates harness tasks automatically.
Includes circuit breaker: after N consecutive task failures from the
same trigger (default 3), the trigger auto-disables. Re-enable
manually via PATCH /api/event-triggers/:id.
New tables: harness_event_triggers (rules + circuit breaker state),
harness_event_log (audit trail + dedup via X-Gitea-Delivery).
This commit is contained in:
@@ -75,6 +75,16 @@ export const agentConfigs = pgTable("harness_agent_configs", {
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// ─── HARNESS: ORCHESTRATOR STATE ──────────────────────────
|
||||
|
||||
export const orchestratorState = pgTable("harness_orchestrator", {
|
||||
id: text("id").primaryKey().default("singleton"),
|
||||
running: boolean("running").default(false).notNull(),
|
||||
currentTaskId: text("current_task_id"),
|
||||
heartbeat: bigint("heartbeat", { mode: "number" }),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// ─── HARNESS: TASKS ────────────────────────────────────────
|
||||
|
||||
export const tasks = pgTable("harness_tasks", {
|
||||
@@ -89,6 +99,7 @@ export const tasks = pgTable("harness_tasks", {
|
||||
project: text("project").notNull().default("—"),
|
||||
evals: jsonb("evals").$type<Record<string, unknown>>().default({}).notNull(),
|
||||
pr: jsonb("pr").$type<{ number: number; title: string; status: string }>(),
|
||||
cancelRequested: boolean("cancel_requested").default(false).notNull(),
|
||||
spec: jsonb("spec").$type<{
|
||||
slug: string;
|
||||
goal: string;
|
||||
@@ -117,3 +128,60 @@ export const iterations = pgTable("harness_iterations", {
|
||||
startedAt: bigint("started_at", { mode: "number" }),
|
||||
completedAt: bigint("completed_at", { mode: "number" }),
|
||||
});
|
||||
|
||||
// ─── HARNESS: EVENT TRIGGERS ───────────────────────────────
|
||||
|
||||
export const eventTriggers = pgTable("harness_event_triggers", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
enabled: boolean("enabled").default(true).notNull(),
|
||||
|
||||
// Matching conditions (all AND'd, null = any)
|
||||
eventType: text("event_type").notNull(), // "status" | "*"
|
||||
repoFilter: text("repo_filter"), // glob: "lazorgurl/*" or exact "lazorgurl/homelab"
|
||||
stateFilter: text("state_filter"), // "failure" | "error" | null
|
||||
contextFilter: text("context_filter"), // substring match on CI context, null = any
|
||||
|
||||
// Task template with {{variable}} placeholders
|
||||
taskTemplate: jsonb("task_template").$type<{
|
||||
slug: string;
|
||||
goal: string;
|
||||
project: string;
|
||||
gitProvider?: string;
|
||||
gitBaseUrl?: string;
|
||||
agentId: string;
|
||||
maxIterations: number;
|
||||
criteria: { label: string; target: string }[];
|
||||
constraints: string[];
|
||||
knowledgeRefs: string[];
|
||||
}>().notNull(),
|
||||
|
||||
// Circuit breaker
|
||||
consecutiveFailures: integer("consecutive_failures").default(0).notNull(),
|
||||
maxConsecutiveFailures: integer("max_consecutive_failures").default(3).notNull(),
|
||||
disabledReason: text("disabled_reason"),
|
||||
|
||||
// Webhook auth (per-trigger override, falls back to GITEA_WEBHOOK_SECRET env)
|
||||
webhookSecret: text("webhook_secret"),
|
||||
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// ─── HARNESS: EVENT LOG ────────────────────────────────────
|
||||
|
||||
export const eventLog = pgTable("harness_event_log", {
|
||||
id: serial("id").primaryKey(),
|
||||
triggerId: text("trigger_id").notNull(),
|
||||
deliveryId: text("delivery_id").notNull().unique(), // X-Gitea-Delivery header (dedup)
|
||||
eventType: text("event_type").notNull(),
|
||||
repo: text("repo").notNull(),
|
||||
commitSha: text("commit_sha"),
|
||||
branch: text("branch"),
|
||||
status: text("status").notNull().default("received"), // received | task_created | skipped | error
|
||||
taskId: text("task_id"),
|
||||
skipReason: text("skip_reason"),
|
||||
error: text("error"),
|
||||
payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user