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
@@ -28,13 +28,13 @@
import Button from '$lib/components/Button.svelte';
import MetaStrip, { type MetaTile } from '$lib/components/MetaStrip.svelte';
/** Grid-select item source lookup — maps descriptor string name to actual function. */
/** Grid-select item source lookup maps descriptor string name to actual function. */
const gridItemSources: Record<string, () => any[]> = {
sortByItems, sortOrderItems, albumModeItems, assetTypeItems, memorySourceItems,
};
/**
* HH:MM, comma-separated: "09:00" or "09:00, 18:30" — the only format cron
* HH:MM, comma-separated: "09:00" or "09:00, 18:30" the only format cron
* dispatch accepts. Matched on blur for time-list fields; invalid values
* are surfaced inline next to the input.
*/
@@ -43,7 +43,7 @@
/** Per-field error messages surfaced inline under time-list inputs. */
let timeListErrors = $state<Record<string, string>>({});
/** Normalize "9:0 , 18:30" в†’ "09:00,18:30" on blur, clear error when valid. */
/** Normalize "9:0 , 18:30" "09:00,18:30" on blur, clear error when valid. */
function normalizeTimeList(key: string) {
const raw = String(form[key] ?? '').trim();
if (!raw) { timeListErrors = { ...timeListErrors, [key]: '' }; return; }
@@ -74,8 +74,8 @@
}
/**
* Quiet-hours preview: "22:00 в†’ 07:00 next day (9h)" or "Quiet period is 0
* minutes — adjust times" when start equals end. Handles overnight ranges
* Quiet-hours preview: "22:00 07:00 next day (9h)" or "Quiet period is 0
* minutes adjust times" when start equals end. Handles overnight ranges
* (start > end) correctly.
*/
function quietHoursPreview(start: string, end: string): string {
@@ -92,8 +92,8 @@
const m = span % 60;
const dur = m === 0 ? `${h}h` : `${h}h ${m}m`;
const arrow = overnight
? `${start} в†’ ${end} ${t('trackingConfig.nextDay')}`
: `${start} в†’ ${end}`;
? `${start} ${end} ${t('trackingConfig.nextDay')}`
: `${start} ${end}`;
return `${arrow} (${dur})`;
}
@@ -112,12 +112,12 @@
/**
* Inline preview of the shipped default template for a scheduled/periodic/
* memory slot. Using the shipped default (not a tracker's current template)
* keeps this scoped to the tracking-config page — which has no concept of
* keeps this scoped to the tracking-config page which has no concept of
* which TemplateConfig a given tracker uses. Users who want to edit the
* actual config can click "Edit template" in the modal footer.
*
* ``previewLocale`` is modal-scoped so switching tabs only refetches for
* this preview — the user's UI locale (and other previews) are untouched.
* this preview the user's UI locale (and other previews) are untouched.
*/
let previewModal = $state<{ slotName: string; rendered: string; error: string; locale: string } | null>(null);
let previewLoading = $state(false);
@@ -262,7 +262,7 @@
if (config.quiet_hours_start && config.quiet_hours_end) {
tiles.push({
icon: 'mdiWeatherNight',
label: `${config.quiet_hours_start}–${config.quiet_hours_end}`,
label: `${config.quiet_hours_start}${config.quiet_hours_end}`,
hint: t('trackingConfig.quietHoursStart'),
tone: 'citrus',
mono: true,
@@ -344,7 +344,7 @@
{/if}
</div>
<!-- Event tracking — driven by descriptor -->
<!-- Event tracking driven by descriptor -->
{#if descriptor}
<fieldset class="border border-[var(--color-border)] rounded-md p-3">
<legend class="text-sm font-medium px-1">{t('trackingConfig.eventTracking')}</legend>
@@ -377,7 +377,7 @@
{/if}
</fieldset>
<!-- Feature sections (periodic, scheduled, memory) — driven by descriptor -->
<!-- Feature sections (periodic, scheduled, memory) driven by descriptor -->
{#each descriptor.featureSections ?? [] as section (section.key)}
<fieldset class="border border-[var(--color-border)] rounded-md p-3">
<legend class="text-sm font-medium px-1">
@@ -494,9 +494,9 @@
</div>
<p class="text-sm text-[var(--color-muted-foreground)] list-row__secondary">
{(desc?.eventFields ?? []).filter(f => (config as Record<string, any>)[f.key]).map(f => t(f.label)).join(', ')}
{config.periodic_enabled ? ` Р’В· ${t('trackingConfig.periodic')}` : ''}
{config.scheduled_enabled ? ` Р’В· ${t('trackingConfig.scheduled')}` : ''}
{config.memory_enabled ? ` Р’В· ${t('trackingConfig.memory')}` : ''}
{config.periodic_enabled ? ` · ${t('trackingConfig.periodic')}` : ''}
{config.scheduled_enabled ? ` · ${t('trackingConfig.scheduled')}` : ''}
{config.memory_enabled ? ` · ${t('trackingConfig.memory')}` : ''}
</p>
</div>
<MetaStrip tiles={trackingConfigTiles(config)} />
@@ -518,7 +518,7 @@
<BlockedByModal open={!!blockedBy} detail={blockedBy} onclose={() => blockedBy = null} />
<Modal open={previewModal !== null}
title={previewModal ? `${t('trackingConfig.previewTemplate')} — ${previewModal.slotName}` : ''}
title={previewModal ? `${t('trackingConfig.previewTemplate')} ${previewModal.slotName}` : ''}
onclose={() => previewModal = null}>
{#if previewModal}
{#if previewLocales.length > 1}
@@ -537,7 +537,7 @@
{t('trackingConfig.previewSampleNote')}
</p>
<!-- Keep the prior rendered/error box mounted while refetching on locale
switch — just dim it. Unmounting and replacing with a small "…"
switch just dim it. Unmounting and replacing with a small ""
placeholder caused a one-frame layout jump as the modal shrank and
then re-expanded. -->
<div class="relative mb-3" style="opacity: {previewLoading ? 0.5 : 1}; transition: opacity 0.15s ease;">
@@ -550,7 +550,7 @@
<pre class="whitespace-pre-wrap text-xs">{@html sanitizePreview(previewModal.rendered)}</pre>
</div>
{:else}
<div class="p-3 text-xs" style="color: var(--color-muted-foreground);">…</div>
<div class="p-3 text-xs" style="color: var(--color-muted-foreground);"></div>
{/if}
</div>
<div class="flex gap-2 justify-end mt-3">