Two 3.3 platform features sharing core infra (notification events, DB
migrations/wiring, config model, API registration), committed together
because they cannot be split at file granularity without broken
intermediate builds.
Notification center:
- New `inboxmessage` table (migration 16) + repo with coalescing
(unread rows with the same dedup_key increment a count; `once` kinds
like update_available notify per-version) and 500-row pruning.
- SystemEvent contract (kind/severity/dedup_key/payload) with new
DownloaderUnavailable / UpdateAvailable / UpdateApplied events;
NotificationManager.send_event persists to the inbox first, then
broadcasts to external providers only when notification.enable.
- SSE `notification` frames driven by an in-process revision counter;
REST endpoints for list/unread-count/read/read-all/delete/clear.
- Wire-ups: qBittorrent auth now reports last_auth_error
(credentials|banned|unreachable) and stops retrying rejected
credentials to avoid a WebUI IP ban; the startup wait loop aborts
early on credential errors and emits a notification; a 24h
update-check tick surfaces new versions; apply/rollback emit results.
- WebUI bell + unread badge + dropdown inbox (severity, coalesce count,
relative time, deep links), SSE-driven with a 30s poll fallback.
LLM provider plugin framework + subscription auth:
- LLMParser refactored to a thin facade over a provider adapter
contract + registry; the three built-ins (openai/anthropic/gemini)
move to adapters with zero behaviour change (existing tests unchanged).
- base_url presets for zai/minimax/deepseek/kimi (mainland + overseas)
reusing the OpenAI-compatible adapter, with a json_object structured-
output fallback for providers without json_schema.
- Downloadable signed plugins: catalog + installer clone the update
system's ed25519 verify / zip-slip-guarded extract pipeline (unsigned
refused, catalog signed against downgrade); versioned-module-name
loader hot-loads without restart.
- Subscription auth: `llmcredential` table (migration 17) stores OAuth
tokens server-side only (never sent to the frontend); device-flow
auth runs a server-side polling task keyed by an opaque handle so the
device_code stays off the wire; AuthExpiredError trips the breaker and
notifies. Two grey-area device-flow plugins (github-copilot,
codex-chatgpt) under plugins/, flagged at-your-own-risk.
- WebUI: grouped provider select (built-in/preset/subscription/
downloadable), install with risk confirmation, device-flow connect
dialog, connected-account chip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs