feat: bridge_self bot commands — status, thresholds, reset, health

Adds bot commands for the bridge_self provider so operators can inspect
and manage bridge health from chat: /status, /thresholds, /reset, /health.
Includes Jinja2 templates for both locales, seed data, capability slots,
and a handler that exposes pending deferred backlog plus per-counter
reset. Also adds .claude/skills/ for project-scoped graph-aware skills.
This commit is contained in:
2026-05-16 03:43:48 +03:00
parent 10d30fc956
commit 8651767112
50 changed files with 1311 additions and 60 deletions
+15 -15
View File
@@ -26,7 +26,7 @@
import ReceiverSection from './ReceiverSection.svelte';
import BotGroupHeader from './BotGroupHeader.svelte';
// в”Ђв”Ђ Helpers в”Ђв”Ђ
// ──── Helpers ────
function getBotName(target: NotificationTarget): string | null {
if (target.type === 'telegram' && target.config?.bot_id) {
@@ -74,7 +74,7 @@
return recv.receiver_key || '?';
}
// в”Ђв”Ђ Constants в”Ђв”Ђ
// ──── Constants ────
const ALL_TYPES = ['telegram', 'webhook', 'email', 'discord', 'slack', 'ntfy', 'matrix', 'broadcast'] as const;
type TargetType = typeof ALL_TYPES[number];
@@ -97,7 +97,7 @@
function targetTiles(target: NotificationTarget): MetaTile[] {
const tiles: MetaTile[] = [];
// Type tile — useful when the "all types" filter is active and rows
// Type tile useful when the "all types" filter is active and rows
// from multiple types appear side-by-side. The receivers count is
// already shown inside the `target-summary` button, so we don't repeat
// it as a tile.
@@ -115,8 +115,8 @@
tone: 'sky',
});
}
// Telegram targets expose a chat label in config — surface it so the
// row reads "Telegram Р’В· @bot Р’В· Family chat" without expanding.
// Telegram targets expose a chat label in config surface it so the
// row reads "Telegram · @bot · Family chat" without expanding.
const cfg = (target.config || {}) as Record<string, any>;
if (target.type === 'telegram' && cfg.chat_id) {
tiles.push({
@@ -126,7 +126,7 @@
mono: true,
});
}
// Webhook target — show host
// Webhook target show host
if (target.type === 'webhook' && cfg.url) {
let host = String(cfg.url);
try { host = new URL(host).host; } catch { /* keep raw */ }
@@ -142,7 +142,7 @@
return tiles;
}
// в”Ђв”Ђ Derived state в”Ђв”Ђ
// ──── Derived state ────
let allTargets = $derived(targetsCache.items);
let activeType = $derived(page.url.searchParams.get('type') as TargetType | null);
@@ -158,7 +158,7 @@
const emailBotItems = $derived(emailBots.map(b => ({ value: b.id, label: b.name, icon: b.icon || 'mdiEmailOutline', desc: b.email })));
const matrixBotItems = $derived(matrixBots.map(b => ({ value: b.id, label: b.name, icon: b.icon || 'mdiMatrix', desc: b.display_name || b.homeserver_url })));
// в”Ђв”Ђ Target form state в”Ђв”Ђ
// ──── Target form state ────
let showForm = $state(false);
let editing = $state<number | null>(null);
@@ -204,7 +204,7 @@
formEl?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// в”Ђв”Ђ Receiver inline form state в”Ђв”Ђ
// ──── Receiver inline form state ────
let addingReceiverForTarget = $state<number | null>(null);
let receiverForm = $state<Record<string, any>>({});
@@ -228,7 +228,7 @@
if (!expandedTargets.has(id)) expandedTargets.add(id);
}
// в”Ђв”Ђ Effects в”Ђв”Ђ
// ──── Effects ────
// Reset form when switching target type tabs
$effect(() => {
@@ -239,11 +239,11 @@
addingReceiverForTarget = null;
});
// в”Ђв”Ђ Data loading в”Ђв”Ђ
// ──── Data loading ────
onMount(load);
// в”Ђв”Ђ Bot grouping в”Ђв”Ђ
// ──── Bot grouping ────
type TargetGroup = {
key: string;
@@ -372,7 +372,7 @@
} catch (e) { console.warn('Failed to load bot chats:', e); }
}
// Active discovery — actually polls Telegram getUpdates and persists any new chats.
// Active discovery actually polls Telegram getUpdates and persists any new chats.
// Fired when the chat picker opens so the user sees the freshest list without a manual click.
async function discoverReceiverBotChats(botId: number) {
if (!botId) return;
@@ -382,7 +382,7 @@
} catch (e) { console.warn('Failed to discover bot chats:', e); }
}
// в”Ђв”Ђ Target CRUD в”Ђв”Ђ
// ──── Target CRUD ────
function openNew() {
form = defaultForm();
@@ -507,7 +507,7 @@
}
}
// в”Ђв”Ђ Receiver CRUD в”Ђв”Ђ
// ──── Receiver CRUD ────
async function openReceiverForm(targetId: number, targetType: string) {
// Force a remount of any picker palette when the same target is reopened