feat(reminders): add generic webhook as a fourth reminder channel (#2952)

Replaces any Discord-specific reminder channel with a generic outbound
webhook channel. Users pick any saved Integration as the target and
supply a JSON payload template with {{title}} and {{message}}
placeholders — values are JSON-escaped before substitution. Works with
Discord, Slack, Teams, ntfy (JSON mode), or any service that accepts a
POST with a JSON body.

- `src/settings.py` — reminder_webhook_integration_id +
  reminder_webhook_payload_template defaults
- `routes/note_routes.py` — webhook delivery block; Integration lookup,
  template rendering, auth wiring; built-in preset defaults so
  discord_webhook works out of the box without a configured template;
  settings_override kwarg avoids test-button race condition
- `routes/auth_routes.py` — discord_webhook preset test handler
- `src/integrations.py` — discord_webhook preset with description +
  example templates; hides auth/key fields in the Integration form
- `src/builtin_actions.py` — webhook_sent delivery check
- `src/tool_implementations.py` — webhook aliases + enum updated
- `static/index.html` — Webhook channel option; Integration picker +
  payload template textarea
- `static/js/settings.js` — Integration list, populateWebhookIntegrations,
  syncChannelRows, hints, load/save, auto-fill preset templates,
  test-button override payload, hide auth/key for URL-auth presets

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Logan Davis
2026-06-05 16:47:57 -04:00
committed by GitHub
parent 2bdf43b74d
commit f72e1bd412
8 changed files with 260 additions and 12 deletions
+3 -1
View File
@@ -1566,6 +1566,8 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
"image gen": "image_gen_enabled", "image generation": "image_gen_enabled",
"reminder channel": "reminder_channel", "reminders": "reminder_channel",
"ntfy topic": "reminder_ntfy_topic",
"webhook integration": "reminder_webhook_integration_id",
"webhook template": "reminder_webhook_payload_template", "webhook payload": "reminder_webhook_payload_template",
"agent tool calls": "agent_max_tool_calls", "max tool calls": "agent_max_tool_calls",
"agent timeout": "agent_stream_timeout_seconds", "stream timeout": "agent_stream_timeout_seconds",
"token budget": "agent_input_token_budget", "input budget": "agent_input_token_budget",
@@ -1581,7 +1583,7 @@ async def do_manage_settings(content: str, owner: Optional[str] = None) -> Dict:
_ENUMS = {
"image_quality": ["low", "medium", "high"],
"reminder_channel": ["browser", "email", "ntfy"],
"reminder_channel": ["browser", "email", "ntfy", "webhook"],
}
def _coerce(value, default):
if isinstance(default, bool):