Add event-driven tasks via Gitea webhooks
Some checks failed
Deploy Production / deploy (push) Failing after 35s
CI / lint-and-test (push) Successful in 33s
CI / build (push) Has been cancelled

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:
Julia McGhee
2026-03-21 21:15:15 +00:00
parent ccebbc4015
commit eeb87018d7
19 changed files with 2368 additions and 35 deletions

View File

@@ -4,13 +4,14 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --port 3100", "dev": "next dev --port 3100",
"build": "next build", "build": "node scripts/build-mcp.mjs && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test": "echo \"no tests yet\"" "test": "echo \"no tests yet\""
}, },
"dependencies": { "dependencies": {
"@homelab/db": "workspace:^", "@homelab/db": "workspace:^",
"@modelcontextprotocol/sdk": "^1.27.1",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
"drizzle-orm": "^0.36.0", "drizzle-orm": "^0.36.0",
@@ -19,14 +20,17 @@
"postgres": "^3.4.0", "postgres": "^3.4.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tsx": "^4.19.0",
"ws": "^8.20.0", "ws": "^8.20.0",
"yaml": "^2.7.0" "yaml": "^2.7.0",
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.0", "@types/node": "^22.10.0",
"@types/react": "^19.0.0", "@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0", "@types/react-dom": "^19.0.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"esbuild": "^0.27.4",
"typescript": "^5.7.0" "typescript": "^5.7.0"
} }
} }

View File

@@ -0,0 +1,19 @@
import { build } from "esbuild";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
await build({
entryPoints: [resolve(__dirname, "../src/mcp-server.ts")],
bundle: true,
platform: "node",
target: "node20",
format: "esm",
outfile: resolve(__dirname, "../dist/mcp-server.mjs"),
external: [],
banner: { js: "#!/usr/bin/env node" },
// Inline all deps — the output is a self-contained script
});
console.log("Built dist/mcp-server.mjs");

View File

@@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from "next/server";
import {
getEventTrigger,
updateEventTrigger,
deleteEventTrigger,
getEventLogByTrigger,
} from "@/lib/event-store";
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const trigger = await getEventTrigger(id);
if (!trigger) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
const recentLogs = await getEventLogByTrigger(id, 20);
return NextResponse.json({ ...trigger, recentLogs });
}
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const body = await request.json();
// Allow re-enabling a circuit-broken trigger
const updates: Record<string, unknown> = {};
if (body.name !== undefined) updates.name = body.name;
if (body.enabled !== undefined) updates.enabled = body.enabled;
if (body.eventType !== undefined) updates.eventType = body.eventType;
if (body.repoFilter !== undefined) updates.repoFilter = body.repoFilter;
if (body.stateFilter !== undefined) updates.stateFilter = body.stateFilter;
if (body.contextFilter !== undefined) updates.contextFilter = body.contextFilter;
if (body.taskTemplate !== undefined) updates.taskTemplate = body.taskTemplate;
if (body.maxConsecutiveFailures !== undefined) updates.maxConsecutiveFailures = body.maxConsecutiveFailures;
if (body.webhookSecret !== undefined) updates.webhookSecret = body.webhookSecret;
// Re-enable: reset circuit breaker
if (body.enabled === true) {
updates.consecutiveFailures = 0;
updates.disabledReason = null;
}
const updated = await updateEventTrigger(id, updates);
if (!updated) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
return NextResponse.json(updated);
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const deleted = await deleteEventTrigger(id);
if (!deleted) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from "next/server";
import {
getAllEventTriggers,
createEventTrigger,
} from "@/lib/event-store";
export async function GET() {
return NextResponse.json(await getAllEventTriggers());
}
export async function POST(request: NextRequest) {
const body = await request.json();
if (!body.name || !body.eventType || !body.taskTemplate) {
return NextResponse.json(
{ error: "name, eventType, and taskTemplate are required" },
{ status: 400 },
);
}
if (!body.taskTemplate.agentId || !body.taskTemplate.goal) {
return NextResponse.json(
{ error: "taskTemplate must include agentId and goal" },
{ status: 400 },
);
}
const trigger = await createEventTrigger({
id: body.id || `evt-${Date.now()}`,
name: body.name,
enabled: body.enabled ?? true,
eventType: body.eventType,
repoFilter: body.repoFilter || null,
stateFilter: body.stateFilter || null,
contextFilter: body.contextFilter || null,
taskTemplate: {
slug: body.taskTemplate.slug || "event-{{sha_short}}",
goal: body.taskTemplate.goal,
project: body.taskTemplate.project || "{{repo}}",
gitProvider: body.taskTemplate.gitProvider,
gitBaseUrl: body.taskTemplate.gitBaseUrl,
agentId: body.taskTemplate.agentId,
maxIterations: body.taskTemplate.maxIterations || 6,
criteria: body.taskTemplate.criteria || [],
constraints: body.taskTemplate.constraints || [],
knowledgeRefs: body.taskTemplate.knowledgeRefs || [],
},
maxConsecutiveFailures: body.maxConsecutiveFailures ?? 3,
webhookSecret: body.webhookSecret || null,
});
return NextResponse.json(trigger, { status: 201 });
}

View File

@@ -8,8 +8,8 @@ import {
export async function GET() { export async function GET() {
return NextResponse.json({ return NextResponse.json({
running: isRunning(), running: await isRunning(),
currentTaskId: currentRunningTaskId(), currentTaskId: await currentRunningTaskId(),
}); });
} }
@@ -18,12 +18,12 @@ export async function POST(request: NextRequest) {
const action = body.action as string; const action = body.action as string;
if (action === "start") { if (action === "start") {
startOrchestrator(); await startOrchestrator();
return NextResponse.json({ ok: true, running: true }); return NextResponse.json({ ok: true, running: true });
} }
if (action === "stop") { if (action === "stop") {
stopOrchestrator(); await stopOrchestrator();
return NextResponse.json({ ok: true, running: false }); return NextResponse.json({ ok: true, running: false });
} }

View File

@@ -20,7 +20,7 @@ export async function POST(
); );
} }
startOrchestrator(); await startOrchestrator();
return NextResponse.json({ ok: true, message: "Orchestrator started, task will be picked up" }); return NextResponse.json({ ok: true, message: "Orchestrator started, task will be picked up" });
} }

View File

@@ -1,6 +1,5 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { getTask } from "@/lib/store"; import { getTask, requestTaskCancel } from "@/lib/store";
import { cancelTask } from "@/lib/orchestrator";
export async function POST( export async function POST(
_request: NextRequest, _request: NextRequest,
@@ -20,11 +19,11 @@ export async function POST(
); );
} }
const cancelled = cancelTask(id); const cancelled = await requestTaskCancel(id);
if (!cancelled) { if (!cancelled) {
return NextResponse.json( return NextResponse.json(
{ error: "Task is not the currently executing task" }, { error: "Failed to request cancellation" },
{ status: 400 }, { status: 500 },
); );
} }

View File

@@ -0,0 +1,158 @@
import { NextRequest, NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";
import { createTask } from "@/lib/store";
import { extractVariables, renderTaskTemplate } from "@/lib/template";
import { findMatchingTriggers } from "@/lib/event-matching";
import {
createEventLogEntry,
getEventLogByDeliveryId,
getEventTrigger,
} from "@/lib/event-store";
import { startOrchestrator } from "@/lib/orchestrator";
import type { Task } from "@/lib/types";
function verifySignature(
rawBody: string,
signature: string,
secret: string,
): boolean {
const computed = createHmac("sha256", secret).update(rawBody).digest("hex");
try {
return timingSafeEqual(
Buffer.from(computed, "hex"),
Buffer.from(signature, "hex"),
);
} catch {
return false;
}
}
export async function POST(request: NextRequest) {
const deliveryId = request.headers.get("x-gitea-delivery") || "";
const eventType = request.headers.get("x-gitea-event") || "";
const signature = request.headers.get("x-gitea-signature") || "";
const rawBody = await request.text();
let payload: Record<string, unknown>;
try {
payload = JSON.parse(rawBody);
} catch {
return NextResponse.json({ error: "invalid JSON" }, { status: 400 });
}
// HMAC validation
const globalSecret = process.env.GITEA_WEBHOOK_SECRET;
if (globalSecret && signature) {
if (!verifySignature(rawBody, signature, globalSecret)) {
return NextResponse.json({ error: "invalid signature" }, { status: 401 });
}
}
// Dedup
if (deliveryId) {
const existing = await getEventLogByDeliveryId(deliveryId);
if (existing) {
return NextResponse.json({ status: "duplicate", deliveryId });
}
}
// Parse event
const event = extractVariables(eventType, payload);
// Find matching triggers
const triggers = await findMatchingTriggers(event);
if (triggers.length === 0) {
return NextResponse.json({
status: "no_match",
eventType,
repo: event.repo,
state: event.state,
});
}
const results: { triggerId: string; taskId: string | null; status: string }[] = [];
for (const trigger of triggers) {
const logDeliveryId = triggers.length > 1
? `${deliveryId || Date.now()}-${trigger.id}`
: deliveryId || String(Date.now());
try {
// Check per-trigger secret if set
if (trigger.webhookSecret && signature) {
if (!verifySignature(rawBody, signature, trigger.webhookSecret)) {
await createEventLogEntry({
triggerId: trigger.id,
deliveryId: logDeliveryId,
eventType,
repo: event.repo,
commitSha: event.sha,
branch: event.branch,
status: "skipped",
skipReason: "Per-trigger signature validation failed",
payload,
});
results.push({ triggerId: trigger.id, taskId: null, status: "skipped" });
continue;
}
}
// Render task spec from template
const spec = renderTaskTemplate(trigger.taskTemplate, event);
const taskId = `task-${Date.now()}-${trigger.id.slice(-4)}`;
const task: Task = {
id: taskId,
slug: spec.slug,
goal: spec.goal,
project: spec.project,
status: "pending",
iteration: 0,
maxIterations: spec.maxIterations,
startedAt: null,
evals: {},
iterations: [],
spec,
};
await createTask(task);
await createEventLogEntry({
triggerId: trigger.id,
deliveryId: logDeliveryId,
eventType,
repo: event.repo,
commitSha: event.sha,
branch: event.branch,
status: "task_created",
taskId,
payload,
});
results.push({ triggerId: trigger.id, taskId, status: "task_created" });
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
await createEventLogEntry({
triggerId: trigger.id,
deliveryId: logDeliveryId,
eventType,
repo: event.repo,
commitSha: event.sha,
branch: event.branch,
status: "error",
error: errorMsg,
payload,
}).catch(() => {});
results.push({ triggerId: trigger.id, taskId: null, status: "error" });
}
}
// Ensure orchestrator is running to pick up new tasks
if (results.some(r => r.status === "task_created")) {
startOrchestrator();
}
return NextResponse.json({ status: "processed", results });
}

View File

@@ -0,0 +1,34 @@
// Match webhook events against event trigger conditions.
import { getEnabledEventTriggers, type EventTrigger } from "./event-store";
import { type ParsedEvent } from "./template";
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function globMatch(pattern: string, value: string): boolean {
const regex = new RegExp(
"^" + pattern.split("*").map(escapeRegex).join(".*") + "$",
);
return regex.test(value);
}
export function matchesTrigger(trigger: EventTrigger, event: ParsedEvent): boolean {
if (!trigger.enabled) return false;
if (trigger.eventType !== "*" && trigger.eventType !== event.eventType) return false;
if (trigger.repoFilter && !globMatch(trigger.repoFilter, event.repo)) return false;
if (trigger.stateFilter && trigger.stateFilter !== event.state) return false;
if (trigger.contextFilter && !event.context.includes(trigger.contextFilter)) return false;
return true;
}
export async function findMatchingTriggers(event: ParsedEvent): Promise<EventTrigger[]> {
const triggers = await getEnabledEventTriggers();
return triggers.filter(t => matchesTrigger(t, event));
}

View File

@@ -0,0 +1,123 @@
import { eq, desc } from "drizzle-orm";
import { db } from "./db";
import { eventTriggers, eventLog } from "@homelab/db";
// ─── TYPES ──────────────────────────────────────────────────
export type EventTrigger = typeof eventTriggers.$inferSelect;
export type EventTriggerInsert = typeof eventTriggers.$inferInsert;
export type EventLogEntry = typeof eventLog.$inferSelect;
// ─── EVENT TRIGGERS ─────────────────────────────────────────
export async function getAllEventTriggers(): Promise<EventTrigger[]> {
return db.select().from(eventTriggers);
}
export async function getEnabledEventTriggers(): Promise<EventTrigger[]> {
return db.select().from(eventTriggers).where(eq(eventTriggers.enabled, true));
}
export async function getEventTrigger(id: string): Promise<EventTrigger | undefined> {
const [row] = await db.select().from(eventTriggers).where(eq(eventTriggers.id, id));
return row;
}
export async function createEventTrigger(trigger: EventTriggerInsert): Promise<EventTrigger> {
const [row] = await db.insert(eventTriggers).values(trigger).returning();
return row;
}
export async function updateEventTrigger(
id: string,
updates: Partial<EventTriggerInsert>,
): Promise<EventTrigger | undefined> {
const [row] = await db
.update(eventTriggers)
.set({ ...updates, updatedAt: new Date() })
.where(eq(eventTriggers.id, id))
.returning();
return row;
}
export async function deleteEventTrigger(id: string): Promise<boolean> {
await db.delete(eventLog).where(eq(eventLog.triggerId, id));
const result = await db.delete(eventTriggers).where(eq(eventTriggers.id, id));
return (result as unknown as { rowCount: number }).rowCount > 0;
}
// ─── EVENT LOG ──────────────────────────────────────────────
export async function createEventLogEntry(
entry: typeof eventLog.$inferInsert,
): Promise<EventLogEntry> {
const [row] = await db.insert(eventLog).values(entry).returning();
return row;
}
export async function getEventLogByDeliveryId(
deliveryId: string,
): Promise<EventLogEntry | undefined> {
const [row] = await db
.select()
.from(eventLog)
.where(eq(eventLog.deliveryId, deliveryId));
return row;
}
export async function getEventLogByTaskId(
taskId: string,
): Promise<EventLogEntry | undefined> {
const [row] = await db
.select()
.from(eventLog)
.where(eq(eventLog.taskId, taskId));
return row;
}
export async function getEventLogByTrigger(
triggerId: string,
limit = 50,
offset = 0,
): Promise<EventLogEntry[]> {
return db
.select()
.from(eventLog)
.where(eq(eventLog.triggerId, triggerId))
.orderBy(desc(eventLog.createdAt))
.limit(limit)
.offset(offset);
}
// ─── CIRCUIT BREAKER ────────────────────────────────────────
export async function recordTaskOutcome(
triggerId: string,
passed: boolean,
): Promise<void> {
const trigger = await getEventTrigger(triggerId);
if (!trigger) return;
if (passed) {
await db
.update(eventTriggers)
.set({ consecutiveFailures: 0, updatedAt: new Date() })
.where(eq(eventTriggers.id, triggerId));
return;
}
const newCount = trigger.consecutiveFailures + 1;
const shouldDisable = newCount >= trigger.maxConsecutiveFailures;
await db
.update(eventTriggers)
.set({
consecutiveFailures: newCount,
enabled: shouldDisable ? false : trigger.enabled,
disabledReason: shouldDisable
? `Circuit breaker: ${newCount} consecutive failures`
: trigger.disabledReason,
updatedAt: new Date(),
})
.where(eq(eventTriggers.id, triggerId));
}

View File

@@ -5,9 +5,15 @@ import {
updateIteration, updateIteration,
getFirstPendingTask, getFirstPendingTask,
getRunningTasks, getRunningTasks,
getOrchestratorState,
setOrchestratorRunning,
setOrchestratorCurrentTask,
updateOrchestratorHeartbeat,
isTaskCancelRequested,
} from "./store"; } from "./store";
import { recordUsage } from "./model-store"; import { recordUsage } from "./model-store";
import { getAgentConfig } from "./agents"; import { getAgentConfig } from "./agents";
import { getEventLogByTaskId, recordTaskOutcome } from "./event-store";
import { getRawCredentialsByProvider } from "./credentials"; import { getRawCredentialsByProvider } from "./credentials";
import { import {
ensureBareClone, ensureBareClone,
@@ -25,47 +31,48 @@ import { evaluate } from "./evaluator";
import { Task, Iteration } from "./types"; import { Task, Iteration } from "./types";
const POLL_INTERVAL_MS = 2000; const POLL_INTERVAL_MS = 2000;
const CANCEL_CHECK_INTERVAL_MS = 3000;
// Local process state — only the AbortController and poll timer live in memory.
// Everything else (running, currentTaskId) is in Postgres.
let pollTimer: ReturnType<typeof setInterval> | null = null; let pollTimer: ReturnType<typeof setInterval> | null = null;
let running = false;
let currentTaskId: string | null = null;
let currentAbort: AbortController | null = null; let currentAbort: AbortController | null = null;
export function isRunning(): boolean { // ─── Public API (all DB-backed) ─────────────────────────────
return running;
export async function isRunning(): Promise<boolean> {
const state = await getOrchestratorState();
return state.running;
} }
export function currentRunningTaskId(): string | null { export async function currentRunningTaskId(): Promise<string | null> {
return currentTaskId; const state = await getOrchestratorState();
return state.currentTaskId;
} }
export function startOrchestrator(): void { export async function startOrchestrator(): Promise<void> {
if (running) return; const state = await getOrchestratorState();
running = true; if (state.running && pollTimer) return;
recoverCrashedTasks(); await setOrchestratorRunning(true);
await recoverCrashedTasks();
pollTimer = setInterval(() => { pollTimer = setInterval(() => {
if (currentTaskId) return;
poll(); poll();
}, POLL_INTERVAL_MS); }, POLL_INTERVAL_MS);
poll(); poll();
} }
export function stopOrchestrator(): void { export async function stopOrchestrator(): Promise<void> {
running = false; await setOrchestratorRunning(false);
if (pollTimer) { if (pollTimer) {
clearInterval(pollTimer); clearInterval(pollTimer);
pollTimer = null; pollTimer = null;
} }
} }
export function cancelTask(taskId: string): boolean { // ─── Internals ──────────────────────────────────────────────
if (currentTaskId !== taskId) return false;
currentAbort?.abort();
return true;
}
async function recoverCrashedTasks(): Promise<void> { async function recoverCrashedTasks(): Promise<void> {
const runningTasks = await getRunningTasks(); const runningTasks = await getRunningTasks();
@@ -84,15 +91,17 @@ async function recoverCrashedTasks(): Promise<void> {
completedAt: Date.now(), completedAt: Date.now(),
}); });
} }
await setOrchestratorCurrentTask(null);
} }
async function poll(): Promise<void> { async function poll(): Promise<void> {
if (!running || currentTaskId) return; const state = await getOrchestratorState();
if (!state.running || state.currentTaskId) return;
const task = await getFirstPendingTask(); const task = await getFirstPendingTask();
if (!task) return; if (!task) return;
currentTaskId = task.id; await setOrchestratorCurrentTask(task.id);
currentAbort = new AbortController(); currentAbort = new AbortController();
try { try {
@@ -104,11 +113,43 @@ async function poll(): Promise<void> {
completedAt: Date.now(), completedAt: Date.now(),
}); });
} finally { } finally {
currentTaskId = null; // Circuit breaker: update event trigger if this task was event-driven
try {
const finalTask = await getTask(task.id);
if (finalTask && (finalTask.status === "completed" || finalTask.status === "failed")) {
const logEntry = await getEventLogByTaskId(task.id);
if (logEntry) {
await recordTaskOutcome(logEntry.triggerId, finalTask.status === "completed");
}
}
} catch (err) {
console.error("[orchestrator] Circuit breaker update failed:", err);
}
await setOrchestratorCurrentTask(null);
currentAbort = null; currentAbort = null;
} }
} }
/**
* Periodically checks the cancel_requested flag in Postgres and triggers
* the local AbortController if set. Returns a cleanup function.
*/
function startCancelWatcher(taskId: string, abort: AbortController): () => void {
const timer = setInterval(async () => {
try {
if (await isTaskCancelRequested(taskId)) {
abort.abort();
clearInterval(timer);
}
} catch {
// DB read failure — don't crash the watcher, just retry next tick
}
}, CANCEL_CHECK_INTERVAL_MS);
return () => clearInterval(timer);
}
async function runTask(task: Task): Promise<void> { async function runTask(task: Task): Promise<void> {
const agentConfig = await getAgentConfig(task.spec.agentId); const agentConfig = await getAgentConfig(task.spec.agentId);
if (!agentConfig) { if (!agentConfig) {
@@ -138,11 +179,15 @@ async function runTask(task: Task): Promise<void> {
startedAt: Date.now(), startedAt: Date.now(),
}); });
// Start watching for cancel_requested in DB
const stopCancelWatcher = startCancelWatcher(task.id, currentAbort!);
let bareClone: string; let bareClone: string;
try { try {
bareClone = await ensureBareClone(repoUrl, task.slug); bareClone = await ensureBareClone(repoUrl, task.slug);
} catch (err) { } catch (err) {
console.error(`[orchestrator] Failed to clone repo for task ${task.id}:`, err); console.error(`[orchestrator] Failed to clone repo for task ${task.id}:`, err);
stopCancelWatcher();
await updateTask(task.id, { await updateTask(task.id, {
status: "failed", status: "failed",
completedAt: Date.now(), completedAt: Date.now(),
@@ -159,11 +204,17 @@ async function runTask(task: Task): Promise<void> {
status: "failed", status: "failed",
completedAt: Date.now(), completedAt: Date.now(),
}); });
stopCancelWatcher();
return; return;
} }
await updateOrchestratorHeartbeat();
const result = await runIteration(task, n, bareClone, branchName); const result = await runIteration(task, n, bareClone, branchName);
if (!result) return; if (!result) {
stopCancelWatcher();
return;
}
if (result.allPassed) { if (result.allPassed) {
converged = true; converged = true;
@@ -171,6 +222,8 @@ async function runTask(task: Task): Promise<void> {
} }
} }
stopCancelWatcher();
if (converged) { if (converged) {
try { try {
const finalTask = await getTask(task.id); const finalTask = await getTask(task.id);

View File

@@ -1,8 +1,77 @@
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { db } from "./db"; import { db } from "./db";
import { tasks as tasksTable, iterations as iterationsTable } from "@homelab/db"; import {
tasks as tasksTable,
iterations as iterationsTable,
orchestratorState as orchTable,
} from "@homelab/db";
import { Task, Iteration } from "./types"; import { Task, Iteration } from "./types";
// ─── ORCHESTRATOR STATE ─────────────────────────────────────
export interface OrchestratorState {
running: boolean;
currentTaskId: string | null;
heartbeat: number | null;
}
async function ensureOrchestratorRow(): Promise<void> {
await db
.insert(orchTable)
.values({ id: "singleton", running: false })
.onConflictDoNothing();
}
export async function getOrchestratorState(): Promise<OrchestratorState> {
await ensureOrchestratorRow();
const [row] = await db.select().from(orchTable).where(eq(orchTable.id, "singleton"));
return {
running: row.running,
currentTaskId: row.currentTaskId ?? null,
heartbeat: row.heartbeat ?? null,
};
}
export async function setOrchestratorRunning(running: boolean): Promise<void> {
await ensureOrchestratorRow();
await db
.update(orchTable)
.set({ running, updatedAt: new Date() })
.where(eq(orchTable.id, "singleton"));
}
export async function setOrchestratorCurrentTask(taskId: string | null): Promise<void> {
await db
.update(orchTable)
.set({ currentTaskId: taskId, heartbeat: Date.now(), updatedAt: new Date() })
.where(eq(orchTable.id, "singleton"));
}
export async function updateOrchestratorHeartbeat(): Promise<void> {
await db
.update(orchTable)
.set({ heartbeat: Date.now(), updatedAt: new Date() })
.where(eq(orchTable.id, "singleton"));
}
// ─── TASK CANCEL FLAG ───────────────────────────────────────
export async function requestTaskCancel(taskId: string): Promise<boolean> {
const result = await db
.update(tasksTable)
.set({ cancelRequested: true, updatedAt: new Date() })
.where(and(eq(tasksTable.id, taskId), eq(tasksTable.status, "running")));
return (result as unknown as { rowCount: number }).rowCount > 0;
}
export async function isTaskCancelRequested(taskId: string): Promise<boolean> {
const [row] = await db
.select({ cancelRequested: tasksTable.cancelRequested })
.from(tasksTable)
.where(eq(tasksTable.id, taskId));
return row?.cancelRequested ?? false;
}
function rowToTask( function rowToTask(
row: typeof tasksTable.$inferSelect, row: typeof tasksTable.$inferSelect,
iters: (typeof iterationsTable.$inferSelect)[], iters: (typeof iterationsTable.$inferSelect)[],

View File

@@ -0,0 +1,95 @@
// Template variable extraction and substitution for event-driven tasks.
export interface ParsedEvent {
eventType: string;
repo: string;
repoName: string;
owner: string;
sha: string;
shaShort: string;
branch: string;
state: string;
context: string;
targetUrl: string;
commitMessage: string;
timestamp: string;
}
export function extractVariables(
eventType: string,
payload: Record<string, unknown>,
): ParsedEvent {
const repository = payload.repository as Record<string, unknown> | undefined;
const owner = repository?.owner as Record<string, unknown> | undefined;
const commit = payload.commit as Record<string, unknown> | undefined;
const branches = payload.branches as { name: string }[] | undefined;
const sha = String(payload.sha || "");
return {
eventType,
repo: String(repository?.full_name || ""),
repoName: String(repository?.name || ""),
owner: String(owner?.login || ""),
sha,
shaShort: sha.slice(0, 7),
branch: branches?.[0]?.name || "",
state: String(payload.state || ""),
context: String(payload.context || ""),
targetUrl: String(payload.target_url || ""),
commitMessage: String(commit?.message || payload.description || ""),
timestamp: new Date().toISOString(),
};
}
export function renderTemplate(
template: string,
vars: ParsedEvent,
): string {
const map: Record<string, string> = {
repo: vars.repo,
repo_name: vars.repoName,
owner: vars.owner,
sha: vars.sha,
sha_short: vars.shaShort,
branch: vars.branch,
state: vars.state,
context: vars.context,
target_url: vars.targetUrl,
commit_message: vars.commitMessage,
timestamp: vars.timestamp,
};
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => map[key] ?? match);
}
export function renderTaskTemplate(
template: {
slug: string;
goal: string;
project: string;
gitProvider?: string;
gitBaseUrl?: string;
agentId: string;
maxIterations: number;
criteria: { label: string; target: string }[];
constraints: string[];
knowledgeRefs: string[];
},
vars: ParsedEvent,
) {
return {
slug: renderTemplate(template.slug, vars),
goal: renderTemplate(template.goal, vars),
project: renderTemplate(template.project, vars),
gitProvider: template.gitProvider as "github" | "gitlab" | "gitea" | undefined,
gitBaseUrl: template.gitBaseUrl,
agentId: template.agentId,
maxIterations: template.maxIterations,
criteria: template.criteria.map(c => ({
label: renderTemplate(c.label, vars),
target: renderTemplate(c.target, vars),
})),
constraints: template.constraints.map(c => renderTemplate(c, vars)),
knowledgeRefs: template.knowledgeRefs.map(k => renderTemplate(k, vars)),
};
}

View File

@@ -0,0 +1,383 @@
#!/usr/bin/env node
/**
* Harness MCP Server
*
* Stdio MCP server that exposes harness knowledge management and task
* orchestration tools to agents spawned by the harness orchestrator.
*
* Environment variables:
* DATABASE_URL — Postgres connection string (required)
* HARNESS_KNOWLEDGE_DIR — Path to knowledge documents directory
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { drizzle } from "drizzle-orm/postgres-js";
import { eq, and } from "drizzle-orm";
import postgres from "postgres";
import {
tasks as tasksTable,
iterations as iterationsTable,
agentConfigs as agentTable,
curatedModels as modelsTable,
orchestratorState as orchTable,
} from "@homelab/db";
import { readFile, writeFile, readdir, mkdir, stat } from "node:fs/promises";
import path from "node:path";
// ── Database ────────────────────────────────────────────────────
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
console.error("DATABASE_URL is required");
process.exit(1);
}
const client = postgres(connectionString);
const db = drizzle(client);
// ── Knowledge dir ───────────────────────────────────────────────
const KNOWLEDGE_DIR = process.env.HARNESS_KNOWLEDGE_DIR || "";
// ── Helpers ─────────────────────────────────────────────────────
function taskSummary(row: typeof tasksTable.$inferSelect) {
return {
id: row.id,
slug: row.slug,
goal: row.goal,
status: row.status,
project: row.project,
iteration: row.iteration,
maxIterations: row.maxIterations,
startedAt: row.startedAt,
completedAt: row.completedAt,
};
}
// ── Server ──────────────────────────────────────────────────────
const server = new McpServer({
name: "harness",
version: "1.0.0",
});
// ── Knowledge Tools ─────────────────────────────────────────────
server.tool(
"knowledge_list",
"List all knowledge documents available in the harness knowledge base",
async () => {
if (!KNOWLEDGE_DIR) {
return { content: [{ type: "text", text: "HARNESS_KNOWLEDGE_DIR not configured" }] };
}
try {
const entries = await readdir(KNOWLEDGE_DIR, { withFileTypes: true });
const files = entries
.filter((e) => e.isFile())
.map((e) => e.name);
return {
content: [{ type: "text", text: files.length > 0 ? files.join("\n") : "(empty)" }],
};
} catch (err) {
return { content: [{ type: "text", text: `Error listing knowledge dir: ${err}` }], isError: true };
}
},
);
server.tool(
"knowledge_read",
"Read a specific knowledge document by filename",
{ filename: z.string().describe("Filename of the knowledge document to read") },
async ({ filename }) => {
if (!KNOWLEDGE_DIR) {
return { content: [{ type: "text", text: "HARNESS_KNOWLEDGE_DIR not configured" }], isError: true };
}
const filePath = path.resolve(KNOWLEDGE_DIR, filename);
// Prevent path traversal
if (!filePath.startsWith(path.resolve(KNOWLEDGE_DIR))) {
return { content: [{ type: "text", text: "Invalid path" }], isError: true };
}
try {
const content = await readFile(filePath, "utf-8");
return { content: [{ type: "text", text: content }] };
} catch (err) {
return { content: [{ type: "text", text: `Error reading ${filename}: ${err}` }], isError: true };
}
},
);
server.tool(
"knowledge_write",
"Create or update a knowledge document in the harness knowledge base",
{
filename: z.string().describe("Filename for the knowledge document (e.g. 'findings.md')"),
content: z.string().describe("Content to write to the knowledge document"),
},
async ({ filename, content }) => {
if (!KNOWLEDGE_DIR) {
return { content: [{ type: "text", text: "HARNESS_KNOWLEDGE_DIR not configured" }], isError: true };
}
const filePath = path.resolve(KNOWLEDGE_DIR, filename);
if (!filePath.startsWith(path.resolve(KNOWLEDGE_DIR))) {
return { content: [{ type: "text", text: "Invalid path" }], isError: true };
}
try {
await mkdir(path.dirname(filePath), { recursive: true });
await writeFile(filePath, content, "utf-8");
return { content: [{ type: "text", text: `Wrote ${filename} (${content.length} bytes)` }] };
} catch (err) {
return { content: [{ type: "text", text: `Error writing ${filename}: ${err}` }], isError: true };
}
},
);
server.tool(
"knowledge_search",
"Search across all knowledge documents for a text pattern (case-insensitive substring match)",
{ query: z.string().describe("Text to search for across all knowledge documents") },
async ({ query }) => {
if (!KNOWLEDGE_DIR) {
return { content: [{ type: "text", text: "HARNESS_KNOWLEDGE_DIR not configured" }], isError: true };
}
try {
const entries = await readdir(KNOWLEDGE_DIR, { withFileTypes: true });
const files = entries.filter((e) => e.isFile());
const results: string[] = [];
const lowerQuery = query.toLowerCase();
for (const file of files) {
const filePath = path.join(KNOWLEDGE_DIR, file.name);
const content = await readFile(filePath, "utf-8");
const lines = content.split("\n");
const matches = lines
.map((line, i) => ({ line, lineNum: i + 1 }))
.filter(({ line }) => line.toLowerCase().includes(lowerQuery));
if (matches.length > 0) {
results.push(
`## ${file.name}\n` +
matches
.slice(0, 10)
.map(({ line, lineNum }) => ` L${lineNum}: ${line.trim()}`)
.join("\n") +
(matches.length > 10 ? `\n ... and ${matches.length - 10} more matches` : ""),
);
}
}
return {
content: [{
type: "text",
text: results.length > 0 ? results.join("\n\n") : `No matches for "${query}"`,
}],
};
} catch (err) {
return { content: [{ type: "text", text: `Error searching: ${err}` }], isError: true };
}
},
);
// ── Task Tools ──────────────────────────────────────────────────
server.tool(
"task_list",
"List all harness tasks with their current status and evaluation results",
async () => {
const rows = await db.select().from(tasksTable);
const tasks = rows.map(taskSummary);
return {
content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }],
};
},
);
server.tool(
"task_get",
"Get full details for a harness task including iteration history and evaluations",
{ taskId: z.string().describe("Task ID to look up") },
async ({ taskId }) => {
const [taskRow] = await db.select().from(tasksTable).where(eq(tasksTable.id, taskId));
if (!taskRow) {
return { content: [{ type: "text", text: `Task ${taskId} not found` }], isError: true };
}
const iters = await db
.select()
.from(iterationsTable)
.where(eq(iterationsTable.taskId, taskId));
const result = {
...taskSummary(taskRow),
spec: taskRow.spec,
evals: taskRow.evals,
pr: taskRow.pr,
iterations: iters
.sort((a, b) => a.n - b.n)
.map((i) => ({
n: i.n,
status: i.status,
diagnosis: i.diagnosis,
evals: i.evals,
diffStats: i.diffStats,
agentOutput: i.agentOutput ? i.agentOutput.slice(-4000) : null,
startedAt: i.startedAt,
completedAt: i.completedAt,
})),
};
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
},
);
server.tool(
"task_create",
"Create a new harness task. The task will be created in 'pending' status and can be started with task_start.",
{
slug: z.string().describe("Unique short identifier for the task (e.g. 'fix-auth-bug')"),
goal: z.string().describe("High-level description of what the task should accomplish"),
project: z.string().describe("Repository in 'owner/repo' format"),
agentId: z.string().describe("ID of the agent configuration to use"),
maxIterations: z.number().optional().describe("Maximum iterations before giving up (default: 6)"),
criteria: z
.array(
z.object({
label: z.string().describe("Criterion name"),
target: z.string().describe("Evaluation target DSL (e.g. 'exitCode:0', 'filesChanged:>0')"),
}),
)
.optional()
.describe("Success criteria for evaluation"),
constraints: z.array(z.string()).optional().describe("Implementation constraints"),
knowledgeRefs: z.array(z.string()).optional().describe("Knowledge document filenames to include in prompt"),
gitProvider: z.enum(["github", "gitlab", "gitea"]).optional().describe("Git provider (default: github)"),
gitBaseUrl: z.string().optional().describe("Base URL for the git provider API"),
},
async (args) => {
const spec = {
slug: args.slug,
goal: args.goal,
project: args.project,
agentId: args.agentId,
maxIterations: args.maxIterations ?? 6,
criteria: args.criteria ?? [],
constraints: args.constraints ?? [],
knowledgeRefs: args.knowledgeRefs ?? [],
gitProvider: args.gitProvider,
gitBaseUrl: args.gitBaseUrl,
};
const taskId = `task-${Date.now()}`;
await db.insert(tasksTable).values({
id: taskId,
slug: spec.slug,
goal: spec.goal,
project: spec.project,
status: "pending",
iteration: 0,
maxIterations: spec.maxIterations,
startedAt: null,
evals: {},
spec,
});
return {
content: [{ type: "text", text: JSON.stringify({ id: taskId, status: "pending", slug: spec.slug }) }],
};
},
);
server.tool(
"task_start",
"Ensure the orchestrator is running so it will pick up pending tasks. Sets orchestrator state to running in the database.",
async () => {
// Ensure the singleton row exists
await db
.insert(orchTable)
.values({ id: "singleton", running: false })
.onConflictDoNothing();
// Set running
await db
.update(orchTable)
.set({ running: true, updatedAt: new Date() })
.where(eq(orchTable.id, "singleton"));
return {
content: [{ type: "text", text: JSON.stringify({ ok: true, message: "Orchestrator set to running — pending tasks will be picked up" }) }],
};
},
);
server.tool(
"task_stop",
"Request cancellation of a running harness task. Sets cancel_requested flag which the orchestrator polls.",
{ taskId: z.string().describe("ID of the running task to cancel") },
async ({ taskId }) => {
const result = await db
.update(tasksTable)
.set({ cancelRequested: true, updatedAt: new Date() })
.where(and(eq(tasksTable.id, taskId), eq(tasksTable.status, "running")));
const rowCount = (result as unknown as { rowCount: number }).rowCount;
if (rowCount === 0) {
return { content: [{ type: "text", text: `Task ${taskId} is not running or not found` }], isError: true };
}
return {
content: [{ type: "text", text: JSON.stringify({ ok: true, message: "Cancellation requested" }) }],
};
},
);
// ── Agent & Model Tools ─────────────────────────────────────────
server.tool(
"agent_list",
"List all configured agent runtimes (agent configs with runtime, model, and provider)",
async () => {
const rows = await db.select().from(agentTable);
const agents = rows.map((r) => ({
id: r.id,
name: r.name,
runtime: r.runtime,
modelId: r.modelId,
provider: r.provider,
}));
return {
content: [{ type: "text", text: JSON.stringify(agents, null, 2) }],
};
},
);
server.tool(
"model_list",
"List available AI models with pricing information",
async () => {
const rows = await db.select().from(modelsTable).where(eq(modelsTable.enabled, true));
const models = rows.map((r) => ({
id: r.id,
name: r.name,
provider: r.provider,
contextWindow: r.contextWindow,
costPer1kInput: r.costPer1kInput,
costPer1kOutput: r.costPer1kOutput,
}));
return {
content: [{ type: "text", text: JSON.stringify(models, null, 2) }],
};
},
);
// ── Start ───────────────────────────────────────────────────────
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error("Harness MCP server failed:", err);
process.exit(1);
});

View File

@@ -0,0 +1,43 @@
CREATE TABLE IF NOT EXISTS "harness_event_log" (
"id" serial PRIMARY KEY NOT NULL,
"trigger_id" text NOT NULL,
"delivery_id" text NOT NULL,
"event_type" text NOT NULL,
"repo" text NOT NULL,
"commit_sha" text,
"branch" text,
"status" text DEFAULT 'received' NOT NULL,
"task_id" text,
"skip_reason" text,
"error" text,
"payload" jsonb NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "harness_event_log_delivery_id_unique" UNIQUE("delivery_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "harness_event_triggers" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"event_type" text NOT NULL,
"repo_filter" text,
"state_filter" text,
"context_filter" text,
"task_template" jsonb NOT NULL,
"consecutive_failures" integer DEFAULT 0 NOT NULL,
"max_consecutive_failures" integer DEFAULT 3 NOT NULL,
"disabled_reason" text,
"webhook_secret" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "harness_orchestrator" (
"id" text PRIMARY KEY DEFAULT 'singleton' NOT NULL,
"running" boolean DEFAULT false NOT NULL,
"current_task_id" text,
"heartbeat" bigint,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "harness_tasks" ADD COLUMN "cancel_requested" boolean DEFAULT false NOT NULL;

View File

@@ -0,0 +1,775 @@
{
"id": "e84e33a6-8185-4839-ad18-37149f19eb32",
"prevId": "629b632c-cf1f-46ff-bb2f-e47d2343ddbd",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.harness_agent_configs": {
"name": "harness_agent_configs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"runtime": {
"name": "runtime",
"type": "text",
"primaryKey": false,
"notNull": true
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"max_tokens": {
"name": "max_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"env": {
"name": "env",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_credentials": {
"name": "harness_credentials",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"label": {
"name": "label",
"type": "text",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"base_url": {
"name": "base_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_curated_models": {
"name": "harness_curated_models",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"context_window": {
"name": "context_window",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"cost_per_1k_input": {
"name": "cost_per_1k_input",
"type": "real",
"primaryKey": false,
"notNull": false
},
"cost_per_1k_output": {
"name": "cost_per_1k_output",
"type": "real",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_event_log": {
"name": "harness_event_log",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"trigger_id": {
"name": "trigger_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"delivery_id": {
"name": "delivery_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"event_type": {
"name": "event_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"repo": {
"name": "repo",
"type": "text",
"primaryKey": false,
"notNull": true
},
"commit_sha": {
"name": "commit_sha",
"type": "text",
"primaryKey": false,
"notNull": false
},
"branch": {
"name": "branch",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'received'"
},
"task_id": {
"name": "task_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"skip_reason": {
"name": "skip_reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"payload": {
"name": "payload",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"harness_event_log_delivery_id_unique": {
"name": "harness_event_log_delivery_id_unique",
"nullsNotDistinct": false,
"columns": [
"delivery_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_event_triggers": {
"name": "harness_event_triggers",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"enabled": {
"name": "enabled",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"event_type": {
"name": "event_type",
"type": "text",
"primaryKey": false,
"notNull": true
},
"repo_filter": {
"name": "repo_filter",
"type": "text",
"primaryKey": false,
"notNull": false
},
"state_filter": {
"name": "state_filter",
"type": "text",
"primaryKey": false,
"notNull": false
},
"context_filter": {
"name": "context_filter",
"type": "text",
"primaryKey": false,
"notNull": false
},
"task_template": {
"name": "task_template",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"consecutive_failures": {
"name": "consecutive_failures",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"max_consecutive_failures": {
"name": "max_consecutive_failures",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 3
},
"disabled_reason": {
"name": "disabled_reason",
"type": "text",
"primaryKey": false,
"notNull": false
},
"webhook_secret": {
"name": "webhook_secret",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_iterations": {
"name": "harness_iterations",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"task_id": {
"name": "task_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"n": {
"name": "n",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"diagnosis": {
"name": "diagnosis",
"type": "text",
"primaryKey": false,
"notNull": false
},
"agent_output": {
"name": "agent_output",
"type": "text",
"primaryKey": false,
"notNull": false
},
"evals": {
"name": "evals",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"diff_stats": {
"name": "diff_stats",
"type": "text",
"primaryKey": false,
"notNull": false
},
"started_at": {
"name": "started_at",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"completed_at": {
"name": "completed_at",
"type": "bigint",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_model_usage": {
"name": "harness_model_usage",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true
},
"task_id": {
"name": "task_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"task_slug": {
"name": "task_slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"iteration": {
"name": "iteration",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"duration_ms": {
"name": "duration_ms",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"timestamp": {
"name": "timestamp",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_orchestrator": {
"name": "harness_orchestrator",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"default": "'singleton'"
},
"running": {
"name": "running",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"current_task_id": {
"name": "current_task_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"heartbeat": {
"name": "heartbeat",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.harness_tasks": {
"name": "harness_tasks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"goal": {
"name": "goal",
"type": "text",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"iteration": {
"name": "iteration",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 0
},
"max_iterations": {
"name": "max_iterations",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 6
},
"started_at": {
"name": "started_at",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"completed_at": {
"name": "completed_at",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"project": {
"name": "project",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'—'"
},
"evals": {
"name": "evals",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"pr": {
"name": "pr",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"cancel_requested": {
"name": "cancel_requested",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"spec": {
"name": "spec",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -8,6 +8,13 @@
"when": 1774124029586, "when": 1774124029586,
"tag": "0000_sparkling_gressill", "tag": "0000_sparkling_gressill",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1774127485727,
"tag": "0001_stale_pride",
"breakpoints": true
} }
] ]
} }

View File

@@ -75,6 +75,16 @@ export const agentConfigs = pgTable("harness_agent_configs", {
updatedAt: timestamp("updated_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 ──────────────────────────────────────── // ─── HARNESS: TASKS ────────────────────────────────────────
export const tasks = pgTable("harness_tasks", { export const tasks = pgTable("harness_tasks", {
@@ -89,6 +99,7 @@ export const tasks = pgTable("harness_tasks", {
project: text("project").notNull().default("—"), project: text("project").notNull().default("—"),
evals: jsonb("evals").$type<Record<string, unknown>>().default({}).notNull(), evals: jsonb("evals").$type<Record<string, unknown>>().default({}).notNull(),
pr: jsonb("pr").$type<{ number: number; title: string; status: string }>(), pr: jsonb("pr").$type<{ number: number; title: string; status: string }>(),
cancelRequested: boolean("cancel_requested").default(false).notNull(),
spec: jsonb("spec").$type<{ spec: jsonb("spec").$type<{
slug: string; slug: string;
goal: string; goal: string;
@@ -117,3 +128,60 @@ export const iterations = pgTable("harness_iterations", {
startedAt: bigint("started_at", { mode: "number" }), startedAt: bigint("started_at", { mode: "number" }),
completedAt: bigint("completed_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(),
});

386
pnpm-lock.yaml generated
View File

@@ -75,6 +75,9 @@ importers:
'@homelab/db': '@homelab/db':
specifier: workspace:^ specifier: workspace:^
version: link:../../packages/db version: link:../../packages/db
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1(zod@4.3.6)
'@xterm/addon-fit': '@xterm/addon-fit':
specifier: ^0.11.0 specifier: ^0.11.0
version: 0.11.0 version: 0.11.0
@@ -99,12 +102,18 @@ importers:
react-dom: react-dom:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.2.4(react@19.2.4) version: 19.2.4(react@19.2.4)
tsx:
specifier: ^4.19.0
version: 4.21.0
ws: ws:
specifier: ^8.20.0 specifier: ^8.20.0
version: 8.20.0 version: 8.20.0
yaml: yaml:
specifier: ^2.7.0 specifier: ^2.7.0
version: 2.8.2 version: 2.8.2
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies: devDependencies:
'@types/node': '@types/node':
specifier: ^22.10.0 specifier: ^22.10.0
@@ -118,6 +127,9 @@ importers:
'@types/ws': '@types/ws':
specifier: ^8.18.1 specifier: ^8.18.1
version: 8.18.1 version: 8.18.1
esbuild:
specifier: ^0.27.4
version: 0.27.4
typescript: typescript:
specifier: ^5.7.0 specifier: ^5.7.0
version: 5.9.3 version: 5.9.3
@@ -698,6 +710,12 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
hasBin: true hasBin: true
'@hono/node-server@1.19.11':
resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
'@humanfs/core@0.19.1': '@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'} engines: {node: '>=18.18.0'}
@@ -870,6 +888,16 @@ packages:
'@js-sdsl/ordered-map@4.4.2': '@js-sdsl/ordered-map@4.4.2':
resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
'@modelcontextprotocol/sdk@1.27.1':
resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@napi-rs/wasm-runtime@0.2.12': '@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
@@ -1937,6 +1965,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
acorn-import-attributes@1.9.5: acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies: peerDependencies:
@@ -1956,9 +1988,20 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv@6.14.0: ajv@6.14.0:
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
ajv@8.18.0:
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2049,6 +2092,10 @@ packages:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
brace-expansion@1.1.12: brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -2139,6 +2186,10 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
content-disposition@1.0.1:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
content-type@1.0.5: content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -2146,6 +2197,10 @@ packages:
cookie-signature@1.0.7: cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
cookie@0.7.2: cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -2542,10 +2597,28 @@ packages:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
eventsource-parser@3.0.6:
resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
engines: {node: '>=18.0.0'}
eventsource@3.0.7:
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
engines: {node: '>=18.0.0'}
express-rate-limit@8.3.1:
resolution: {integrity: sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==}
engines: {node: '>= 16'}
peerDependencies:
express: '>= 4.11'
express@4.22.1: express@4.22.1:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
express@5.2.1:
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
engines: {node: '>= 18'}
extend@3.0.2: extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -2562,6 +2635,9 @@ packages:
fast-levenshtein@2.0.6: fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
fastq@1.20.1: fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -2590,6 +2666,10 @@ packages:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
finalhandler@2.1.1:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
find-up@5.0.0: find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -2623,6 +2703,10 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2723,6 +2807,10 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
hono@4.12.8:
resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==}
engines: {node: '>=16.9.0'}
http-errors@2.0.1: http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -2735,6 +2823,10 @@ packages:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
iconv-lite@0.7.2:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -2762,6 +2854,10 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ip-address@10.1.0:
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
engines: {node: '>= 12'}
ipaddr.js@1.9.1: ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@@ -2837,6 +2933,9 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
is-regex@1.2.1: is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2887,6 +2986,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true hasBin: true
jose@6.2.2:
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
joycon@3.1.1: joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -2907,6 +3009,12 @@ packages:
json-schema-traverse@0.4.1: json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json-schema-typed@8.0.2:
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
json-stable-stringify-without-jsonify@1.0.1: json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -3041,9 +3149,17 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
merge-descriptors@1.0.3: merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
merge-descriptors@2.0.0:
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
engines: {node: '>=18'}
merge2@1.4.1: merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -3060,10 +3176,18 @@ packages:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@2.1.35: mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
mime@1.6.0: mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -3111,6 +3235,10 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next@15.5.14: next@15.5.14:
resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==} resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -3191,6 +3319,9 @@ packages:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -3229,6 +3360,9 @@ packages:
path-to-regexp@0.1.12: path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
pathe@2.0.3: pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -3271,6 +3405,10 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
pkce-challenge@5.0.1:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
pkg-types@1.3.1: pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -3369,6 +3507,10 @@ packages:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
react-dom@19.2.4: react-dom@19.2.4:
resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies: peerDependencies:
@@ -3401,6 +3543,10 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
require-in-the-middle@8.0.1: require-in-the-middle@8.0.1:
resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==}
engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'}
@@ -3435,6 +3581,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
router@2.2.0:
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
engines: {node: '>= 18'}
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -3476,10 +3626,18 @@ packages:
resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
serve-static@1.16.3: serve-static@1.16.3:
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3709,6 +3867,10 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
type-is@2.0.1:
resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
engines: {node: '>= 0.6'}
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3791,6 +3953,9 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.20.0: ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@@ -3828,6 +3993,14 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
zod-to-json-schema@3.25.1:
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
peerDependencies:
zod: ^3.25 || ^4
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
snapshots: snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
@@ -4131,6 +4304,10 @@ snapshots:
protobufjs: 7.5.4 protobufjs: 7.5.4
yargs: 17.7.2 yargs: 17.7.2
'@hono/node-server@1.19.11(hono@4.12.8)':
dependencies:
hono: 4.12.8
'@humanfs/core@0.19.1': {} '@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7': '@humanfs/node@0.16.7':
@@ -4260,6 +4437,28 @@ snapshots:
'@js-sdsl/ordered-map@4.4.2': {} '@js-sdsl/ordered-map@4.4.2': {}
'@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)':
dependencies:
'@hono/node-server': 1.19.11(hono@4.12.8)
ajv: 8.18.0
ajv-formats: 3.0.1(ajv@8.18.0)
content-type: 1.0.5
cors: 2.8.6
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.3.1(express@5.2.1)
hono: 4.12.8
jose: 6.2.2
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 4.3.6
zod-to-json-schema: 3.25.1(zod@4.3.6)
transitivePeerDependencies:
- supports-color
'@napi-rs/wasm-runtime@0.2.12': '@napi-rs/wasm-runtime@0.2.12':
dependencies: dependencies:
'@emnapi/core': 1.9.1 '@emnapi/core': 1.9.1
@@ -5453,6 +5652,11 @@ snapshots:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
negotiator: 1.0.0
acorn-import-attributes@1.9.5(acorn@8.16.0): acorn-import-attributes@1.9.5(acorn@8.16.0):
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.16.0
@@ -5465,6 +5669,10 @@ snapshots:
agent-base@7.1.4: {} agent-base@7.1.4: {}
ajv-formats@3.0.1(ajv@8.18.0):
optionalDependencies:
ajv: 8.18.0
ajv@6.14.0: ajv@6.14.0:
dependencies: dependencies:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@@ -5472,6 +5680,13 @@ snapshots:
json-schema-traverse: 0.4.1 json-schema-traverse: 0.4.1
uri-js: 4.4.1 uri-js: 4.4.1
ajv@8.18.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.0
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
ansi-styles@4.3.0: ansi-styles@4.3.0:
@@ -5590,6 +5805,20 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.3
http-errors: 2.0.1
iconv-lite: 0.7.2
on-finished: 2.4.1
qs: 6.14.2
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -5672,10 +5901,14 @@ snapshots:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
content-disposition@1.0.1: {}
content-type@1.0.5: {} content-type@1.0.5: {}
cookie-signature@1.0.7: {} cookie-signature@1.0.7: {}
cookie-signature@1.2.2: {}
cookie@0.7.2: {} cookie@0.7.2: {}
cors@2.8.6: cors@2.8.6:
@@ -6184,6 +6417,17 @@ snapshots:
etag@1.8.1: {} etag@1.8.1: {}
eventsource-parser@3.0.6: {}
eventsource@3.0.7:
dependencies:
eventsource-parser: 3.0.6
express-rate-limit@8.3.1(express@5.2.1):
dependencies:
express: 5.2.1
ip-address: 10.1.0
express@4.22.1: express@4.22.1:
dependencies: dependencies:
accepts: 1.3.8 accepts: 1.3.8
@@ -6220,6 +6464,39 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
express@5.2.1:
dependencies:
accepts: 2.0.0
body-parser: 2.2.2
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
mime-types: 3.0.2
on-finished: 2.4.1
once: 1.4.0
parseurl: 1.3.3
proxy-addr: 2.0.7
qs: 6.14.2
range-parser: 1.2.1
router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
type-is: 2.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
extend@3.0.2: {} extend@3.0.2: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
@@ -6236,6 +6513,8 @@ snapshots:
fast-levenshtein@2.0.6: {} fast-levenshtein@2.0.6: {}
fast-uri@3.1.0: {}
fastq@1.20.1: fastq@1.20.1:
dependencies: dependencies:
reusify: 1.1.0 reusify: 1.1.0
@@ -6269,6 +6548,17 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
finalhandler@2.1.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
find-up@5.0.0: find-up@5.0.0:
dependencies: dependencies:
locate-path: 6.0.0 locate-path: 6.0.0
@@ -6301,6 +6591,8 @@ snapshots:
fresh@0.5.2: {} fresh@0.5.2: {}
fresh@2.0.0: {}
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@@ -6408,6 +6700,8 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
hono@4.12.8: {}
http-errors@2.0.1: http-errors@2.0.1:
dependencies: dependencies:
depd: 2.0.0 depd: 2.0.0
@@ -6427,6 +6721,10 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
ignore@5.3.2: {} ignore@5.3.2: {}
ignore@7.0.5: {} ignore@7.0.5: {}
@@ -6453,6 +6751,8 @@ snapshots:
hasown: 2.0.2 hasown: 2.0.2
side-channel: 1.1.0 side-channel: 1.1.0
ip-address@10.1.0: {}
ipaddr.js@1.9.1: {} ipaddr.js@1.9.1: {}
is-array-buffer@3.0.5: is-array-buffer@3.0.5:
@@ -6530,6 +6830,8 @@ snapshots:
is-number@7.0.0: {} is-number@7.0.0: {}
is-promise@4.0.0: {}
is-regex@1.2.1: is-regex@1.2.1:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -6584,6 +6886,8 @@ snapshots:
jiti@2.6.1: {} jiti@2.6.1: {}
jose@6.2.2: {}
joycon@3.1.1: {} joycon@3.1.1: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
@@ -6600,6 +6904,10 @@ snapshots:
json-schema-traverse@0.4.1: {} json-schema-traverse@0.4.1: {}
json-schema-traverse@1.0.0: {}
json-schema-typed@8.0.2: {}
json-stable-stringify-without-jsonify@1.0.1: {} json-stable-stringify-without-jsonify@1.0.1: {}
json5@1.0.2: json5@1.0.2:
@@ -6705,8 +7013,12 @@ snapshots:
media-typer@0.3.0: {} media-typer@0.3.0: {}
media-typer@1.1.0: {}
merge-descriptors@1.0.3: {} merge-descriptors@1.0.3: {}
merge-descriptors@2.0.0: {}
merge2@1.4.1: {} merge2@1.4.1: {}
methods@1.1.2: {} methods@1.1.2: {}
@@ -6718,10 +7030,16 @@ snapshots:
mime-db@1.52.0: {} mime-db@1.52.0: {}
mime-db@1.54.0: {}
mime-types@2.1.35: mime-types@2.1.35:
dependencies: dependencies:
mime-db: 1.52.0 mime-db: 1.52.0
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
mime@1.6.0: {} mime@1.6.0: {}
minimatch@10.2.4: minimatch@10.2.4:
@@ -6761,6 +7079,8 @@ snapshots:
negotiator@0.6.3: {} negotiator@0.6.3: {}
negotiator@1.0.0: {}
next@15.5.14(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): next@15.5.14(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@next/env': 15.5.14 '@next/env': 15.5.14
@@ -6854,6 +7174,10 @@ snapshots:
dependencies: dependencies:
ee-first: 1.1.1 ee-first: 1.1.1
once@1.4.0:
dependencies:
wrappy: 1.0.2
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -6891,6 +7215,8 @@ snapshots:
path-to-regexp@0.1.12: {} path-to-regexp@0.1.12: {}
path-to-regexp@8.3.0: {}
pathe@2.0.3: {} pathe@2.0.3: {}
pg-int8@1.0.1: {} pg-int8@1.0.1: {}
@@ -6940,6 +7266,8 @@ snapshots:
pirates@4.0.7: {} pirates@4.0.7: {}
pkce-challenge@5.0.1: {}
pkg-types@1.3.1: pkg-types@1.3.1:
dependencies: dependencies:
confbox: 0.1.8 confbox: 0.1.8
@@ -7032,6 +7360,13 @@ snapshots:
iconv-lite: 0.4.24 iconv-lite: 0.4.24
unpipe: 1.0.0 unpipe: 1.0.0
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.1
iconv-lite: 0.7.2
unpipe: 1.0.0
react-dom@19.2.4(react@19.2.4): react-dom@19.2.4(react@19.2.4):
dependencies: dependencies:
react: 19.2.4 react: 19.2.4
@@ -7067,6 +7402,8 @@ snapshots:
require-directory@2.1.1: {} require-directory@2.1.1: {}
require-from-string@2.0.2: {}
require-in-the-middle@8.0.1: require-in-the-middle@8.0.1:
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
@@ -7128,6 +7465,16 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.59.0 '@rollup/rollup-win32-x64-msvc': 4.59.0
fsevents: 2.3.3 fsevents: 2.3.3
router@2.2.0:
dependencies:
debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
path-to-regexp: 8.3.0
transitivePeerDependencies:
- supports-color
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
@@ -7181,6 +7528,22 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
serve-static@1.16.3: serve-static@1.16.3:
dependencies: dependencies:
encodeurl: 2.0.0 encodeurl: 2.0.0
@@ -7190,6 +7553,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 1.2.1
transitivePeerDependencies:
- supports-color
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@@ -7490,6 +7862,12 @@ snapshots:
media-typer: 0.3.0 media-typer: 0.3.0
mime-types: 2.1.35 mime-types: 2.1.35
type-is@2.0.1:
dependencies:
content-type: 1.0.5
media-typer: 1.1.0
mime-types: 3.0.2
typed-array-buffer@1.0.3: typed-array-buffer@1.0.3:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -7625,6 +8003,8 @@ snapshots:
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
wrappy@1.0.2: {}
ws@8.20.0: {} ws@8.20.0: {}
xtend@4.0.2: {} xtend@4.0.2: {}
@@ -7646,3 +8026,9 @@ snapshots:
yargs-parser: 21.1.1 yargs-parser: 21.1.1
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod-to-json-schema@3.25.1(zod@4.3.6):
dependencies:
zod: 4.3.6
zod@4.3.6: {}