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).
188 lines
7.6 KiB
TypeScript
188 lines
7.6 KiB
TypeScript
import {
|
|
pgTable,
|
|
serial,
|
|
text,
|
|
timestamp,
|
|
boolean,
|
|
integer,
|
|
real,
|
|
jsonb,
|
|
bigint,
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
// ─── EXISTING ───────────────────────────────────────────────
|
|
|
|
export const users = pgTable("users", {
|
|
id: serial("id").primaryKey(),
|
|
email: text("email").notNull().unique(),
|
|
name: text("name"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// ─── HARNESS: CREDENTIALS ──────────────────────────────────
|
|
|
|
export const credentials = pgTable("harness_credentials", {
|
|
id: text("id").primaryKey(),
|
|
provider: text("provider").notNull(),
|
|
label: text("label").notNull(),
|
|
token: text("token").notNull(),
|
|
baseUrl: text("base_url"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// ─── HARNESS: CURATED MODELS ───────────────────────────────
|
|
|
|
export const curatedModels = pgTable("harness_curated_models", {
|
|
id: text("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
provider: text("provider").notNull(),
|
|
enabled: boolean("enabled").default(true).notNull(),
|
|
contextWindow: integer("context_window"),
|
|
costPer1kInput: real("cost_per_1k_input"),
|
|
costPer1kOutput: real("cost_per_1k_output"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// ─── HARNESS: MODEL USAGE ──────────────────────────────────
|
|
|
|
export const modelUsage = pgTable("harness_model_usage", {
|
|
id: serial("id").primaryKey(),
|
|
modelId: text("model_id").notNull(),
|
|
provider: text("provider").notNull(),
|
|
taskId: text("task_id").notNull(),
|
|
taskSlug: text("task_slug").notNull(),
|
|
iteration: integer("iteration").notNull(),
|
|
inputTokens: integer("input_tokens").notNull(),
|
|
outputTokens: integer("output_tokens").notNull(),
|
|
durationMs: integer("duration_ms").notNull(),
|
|
timestamp: bigint("timestamp", { mode: "number" }).notNull(),
|
|
});
|
|
|
|
// ─── HARNESS: AGENT CONFIGS ────────────────────────────────
|
|
|
|
export const agentConfigs = pgTable("harness_agent_configs", {
|
|
id: text("id").primaryKey(),
|
|
name: text("name").notNull(),
|
|
runtime: text("runtime").notNull(), // "claude-code" | "codex" | "opencode"
|
|
modelId: text("model_id").notNull(),
|
|
provider: text("provider").notNull(),
|
|
maxTokens: integer("max_tokens"),
|
|
env: jsonb("env").$type<Record<string, string>>(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
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", {
|
|
id: text("id").primaryKey(),
|
|
slug: text("slug").notNull(),
|
|
goal: text("goal").notNull(),
|
|
status: text("status").notNull().default("pending"), // pending | running | completed | failed
|
|
iteration: integer("iteration").notNull().default(0),
|
|
maxIterations: integer("max_iterations").notNull().default(6),
|
|
startedAt: bigint("started_at", { mode: "number" }),
|
|
completedAt: bigint("completed_at", { mode: "number" }),
|
|
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;
|
|
project: string;
|
|
agentId: string;
|
|
maxIterations: number;
|
|
criteria: { label: string; target: string }[];
|
|
constraints: string[];
|
|
knowledgeRefs: string[];
|
|
}>().notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
|
});
|
|
|
|
// ─── HARNESS: ITERATIONS ───────────────────────────────────
|
|
|
|
export const iterations = pgTable("harness_iterations", {
|
|
id: serial("id").primaryKey(),
|
|
taskId: text("task_id").notNull(),
|
|
n: integer("n").notNull(),
|
|
status: text("status").notNull().default("pending"), // pending | running | passed | failed
|
|
diagnosis: text("diagnosis"),
|
|
agentOutput: text("agent_output"),
|
|
evals: jsonb("evals").$type<Record<string, unknown>>(),
|
|
diffStats: text("diff_stats"),
|
|
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(),
|
|
});
|