import { loadRepoById } from "../repos";
import { notifyQuietly } from "./deliver";
import { accountAdmins, repoAdmins } from "./recipients";
const OFFLINE_AFTER_MS = 10 * 60 * 1000;
/** Runners silent for longer than this are considered abandoned and no longer reported. */
const FORGET_AFTER_MS = 24 * 60 * 60 * 1000;
const RUNNERS_PER_SWEEP = 50;
type RunnerRow = { id: string; name: string; repo_id: string | null; owner_id: string | null; last_seen_at: number };
async function notifyRunnerOffline(runner: RunnerRow): Promise<void> {
const minutes = Math.round((Date.now() - runner.last_seen_at) / 60_000);
const details = [
`Runner "${runner.name}" has not checked in for ${minutes} minutes.`,
"Queued jobs for it will wait.",
];
if (runner.repo_id) {
const repo = await loadRepoById(runner.repo_id);
if (!repo) return;
await notifyQuietly({
category: "runner",
repoId: repo.id,
accountId: null,
access: "admin",
recipients: await repoAdmins(repo.id),
title: `Runner ${runner.name} is offline (${repo.owner_name}/${repo.name})`,
details,
url: `/${repo.owner_name}/${repo.name}/settings/runners`,
});
return;
}
if (!runner.owner_id) return;
await notifyQuietly({
category: "runner",
repoId: null,
accountId: runner.owner_id,
access: "account",
recipients: await accountAdmins(runner.owner_id),
title: `Runner ${runner.name} is offline`,