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 { EventEmitter } from "node:events";
import { execSync } from "node:child_process";
import { appendFile, mkdir, readFile, writeFile, readdir, rename, unlink } from "node:fs/promises";
import { join, sep } from "node:path";
import { existsSync, watch, type FSWatcher } from "node:fs";
import { existsSync, watch, type FSWatcher, readFileSync } from "node:fs";
import type { Task, TaskDetail, TaskCreateInput, TaskAttachment, AgentLogEntry, BoardConfig, Column, MergeResult, Settings } from "./types.js";
import { VALID_TRANSITIONS, DEFAULT_SETTINGS } from "./types.js";
@@ -1174,6 +1174,13 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
? task.dependencies.map((d) => `- **Task:** ${d}`).join("\n")
: "- **None**";
// Get current settings to check for ntfy configuration
const settings = this.getSettingsSync();
const notificationsSection =
settings.ntfyEnabled && settings.ntfyTopic
? `\n## Notifications\n\nntfy topic: \`${settings.ntfyTopic}\`\n`
: "";
const heading = task.title ? `${task.id}: ${task.title}` : task.id;
return `# ${heading}
@@ -1209,6 +1216,23 @@ ${deps}
- [ ] All steps complete
- [ ] All tests passing
`;
${notificationsSection}`;
}
/**
* Synchronous version of getSettings for internal use.
* Returns cached settings or default settings if not loaded.
*/
private getSettingsSync(): Settings {
// Since we can't easily make generateSpecifiedPrompt async,
// we read settings synchronously from the file.
// The settings file is read during init and on each update,
// so this should be reasonably up-to-date for prompt generation.
try {
const config = JSON.parse(readFileSync(this.configPath, "utf-8"));
return { ...DEFAULT_SETTINGS, ...config.settings };
} catch {
return DEFAULT_SETTINGS;
}
}
}

View File

@@ -209,6 +209,12 @@ export interface Settings {
* remain in triage with status "awaiting-approval" until a user approves
* or rejects the plan. Default: false. */
requirePlanApproval?: boolean;
/** ntfy.sh topic name for push notifications. When set along with ntfyEnabled,
* notifications are sent to https://ntfy.sh/{topic} when tasks complete or fail. */
ntfyTopic?: string;
/** When true, enables ntfy.sh push notifications for task completion and failures.
* Requires ntfyTopic to be set. Default: false. */
ntfyEnabled?: boolean;
}
export const DEFAULT_SETTINGS: Settings = {
@@ -229,6 +235,8 @@ export const DEFAULT_SETTINGS: Settings = {
autoResolveConflicts: true,
smartConflictResolution: true,
requirePlanApproval: false,
ntfyEnabled: false,
ntfyTopic: undefined,
};
export interface BoardConfig {