Initial commit: Discord-Claude Gateway with event-driven agent runtime

This commit is contained in:
2026-02-22 00:31:25 -05:00
commit 77d7c74909
58 changed files with 11772 additions and 0 deletions

112
src/cron-scheduler.ts Normal file
View File

@@ -0,0 +1,112 @@
import cron from "node-cron";
import type { Event } from "./event-queue.js";
export interface CronJob {
name: string;
expression: string;
instruction: string;
}
type EnqueueFn = (event: Omit<Event, "id" | "timestamp">) => Event | null;
export class CronScheduler {
private tasks: Map<string, cron.ScheduledTask> = new Map();
parseConfig(content: string): CronJob[] {
const jobs: CronJob[] = [];
const lines = content.split("\n");
// Find the "## Cron Jobs" section
let inCronSection = false;
let currentName: string | null = null;
let currentExpression: string | null = null;
let currentInstruction: string | null = null;
for (const line of lines) {
// Detect start of "## Cron Jobs" section
const h2Match = line.match(/^##\s+(.+)$/);
if (h2Match) {
if (inCronSection) {
// We hit another ## heading — check if it's a ### job or end of section
// A ## heading ends the Cron Jobs section
if (currentName !== null && currentExpression !== null && currentInstruction !== null) {
jobs.push({ name: currentName, expression: currentExpression, instruction: currentInstruction });
}
currentName = null;
currentExpression = null;
currentInstruction = null;
inCronSection = false;
}
if (h2Match[1].trim() === "Cron Jobs") {
inCronSection = true;
}
continue;
}
if (!inCronSection) continue;
// Detect ### job name headers within the Cron Jobs section
const h3Match = line.match(/^###\s+(.+)$/);
if (h3Match) {
// Push previous job if complete
if (currentName !== null && currentExpression !== null && currentInstruction !== null) {
jobs.push({ name: currentName, expression: currentExpression, instruction: currentInstruction });
}
currentName = h3Match[1].trim();
currentExpression = null;
currentInstruction = null;
continue;
}
const cronMatch = line.match(/^Cron:\s*(.+)$/);
if (cronMatch && currentName !== null) {
currentExpression = cronMatch[1].trim();
continue;
}
const instructionMatch = line.match(/^Instruction:\s*(.+)$/);
if (instructionMatch && currentName !== null) {
currentInstruction = instructionMatch[1].trim();
continue;
}
}
// Push the last job if complete
if (inCronSection && currentName !== null && currentExpression !== null && currentInstruction !== null) {
jobs.push({ name: currentName, expression: currentExpression, instruction: currentInstruction });
}
return jobs;
}
start(jobs: CronJob[], enqueue: EnqueueFn): void {
for (const job of jobs) {
if (!cron.validate(job.expression)) {
console.warn(
`Cron job "${job.name}" has invalid cron expression "${job.expression}". Skipping.`
);
continue;
}
const task = cron.schedule(job.expression, () => {
enqueue({
type: "cron",
payload: {
instruction: job.instruction,
jobName: job.name,
},
source: "cron-scheduler",
});
});
this.tasks.set(job.name, task);
}
}
stop(): void {
for (const task of this.tasks.values()) {
task.stop();
}
this.tasks.clear();
}
}