feat(KB-026): add ntfy.sh push notification support

- Add ntfy settings (ntfyEnabled, ntfyTopic) to core types and config storage
- Create NtfyNotifier service with event-based notifications for task completion/failure
- Add Notifications section to SettingsModal for configuring ntfy topic
- Wire up NtfyNotifier in engine to send notifications on task state changes
- Include ntfy topic in task templates for per-task notification override
- Add comprehensive tests for NtfyNotifier service
This commit is contained in:
gsxdsm
2026-03-29 19:46:35 -07:00
parent ad94c51836
commit 0eb3315911
11 changed files with 974 additions and 8 deletions

View File

@@ -2,7 +2,7 @@ import { exec } from "node:child_process";
import type { AddressInfo } from "node:net";
import { TaskStore } from "@kb/core";
import { createServer } from "@kb/dashboard";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees } from "@kb/engine";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees, NtfyNotifier } from "@kb/engine";
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
function openBrowser(url: string): void {
@@ -19,6 +19,10 @@ export async function runDashboard(port: number, opts: { open?: boolean; paused?
await store.init();
await store.watch();
// ── NtfyNotifier: push notifications for task completion and failures ─
const notifier = new NtfyNotifier(store);
notifier.start();
// Set enginePaused if starting in paused mode
if (opts.paused) {
await store.updateSettings({ enginePaused: true });
@@ -375,6 +379,7 @@ export async function runDashboard(port: number, opts: { open?: boolean; paused?
process.on("SIGINT", () => {
triage.stop();
scheduler.stop();
notifier.stop();
if (mergeRetryTimer) clearTimeout(mergeRetryTimer);
store.stopWatching();
process.exit(0);
@@ -384,6 +389,7 @@ export async function runDashboard(port: number, opts: { open?: boolean; paused?
// Dev mode: simplified SIGINT handler (no engine components)
if (opts.dev) {
process.on("SIGINT", () => {
notifier.stop();
store.stopWatching();
process.exit(0);
});