Add bot schedules (routines), open Activity, add Help and a Desktop computer view

Bots create, list, update and delete schedules (cron_jobs, Bun.cron in UTC);
a per-minute cron queues due runs as tasks. Routines, Activity (super admins)
and Help get their own rail aside. Help covers familiar problems, starting
with signing in to Google through Cookie-Editor over the new Desktop (noVNC)
view.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 10:52:32 +01:00
co-authored by Claude Opus 5.5
parent f24c0d7e82
commit c44048dbab
62 changed files with 1904 additions and 78 deletions
+9
View File
@@ -3,6 +3,7 @@ import cronHeartbeat from "./heartbeat";
import pruneBotsQueue from "./prune-bots-queue";
import cronSyncHarnessModels from "./sync-harness-models";
import syncComputers from "./sync-computers";
import runCronJobs from "./run-cron-jobs";
import registerCronJob from "./register-cron-job";
export default function cron() {
@@ -38,4 +39,12 @@ export default function cron() {
task: syncComputers,
run_on_start: true,
});
registerCronJob({
title: "run-cron-jobs",
schedule: "* * * * *",
task: runCronJobs,
run_on_start: true,
quiet: true,
});
}
+13 -5
View File
@@ -5,6 +5,11 @@ type Params = {
schedule: Bun.CronWithAutocomplete;
task: () => Promise<unknown>;
run_on_start?: boolean;
/**
* Don't log every run (for jobs that run every minute). Errors are
* still logged.
*/
quiet?: boolean;
};
/**
@@ -18,14 +23,17 @@ export default function registerCronJob({
schedule,
task,
run_on_start,
quiet,
}: Params) {
const run = async () => {
try {
await logEvent({
title: `Cron: ${title}`,
category: "cron",
plain_text: `CRON: ${title} ...`,
});
if (!quiet) {
await logEvent({
title: `Cron: ${title}`,
category: "cron",
plain_text: `CRON: ${title} ...`,
});
}
await task();
} catch (error) {
await logError({
+23
View File
@@ -0,0 +1,23 @@
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_COBBLE_CRON_JOBS } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import fireCronJob from "@/src/functions/backend/db/cron-jobs/fire-cron-job";
/**
* Queues the bots' due schedules (`cron_jobs`), oldest due first, at most
* `AppData.CronJobBatchSize` a tick. A schedule missed while the server
* was down runs once on the next tick, then carries on from now.
*/
export default async function runCronJobs() {
const now = Date.now();
const res = await BunSQLite.sql<BUN_SQLITE_COBBLE_CRON_JOBS>({
sql: `SELECT * FROM cron_jobs WHERE enabled = 1 AND next_run_at IS NOT NULL AND next_run_at <= ?
ORDER BY next_run_at ASC LIMIT ?`,
values: [now, AppData.CronJobBatchSize],
});
for (const cron_job of res.payload || []) {
await fireCronJob({ cron_job, now });
}
}