v0.18.0
Follow-ups from 0.17.0
Three of 0.17.0's ADRs named a remaining follow-up; this release closes them.
Presets get their admin surface
0.17.0 shipped configuration presets as admin-gated AJAX endpoints; the Configurations backend module now puts them in front of the admin. A pending-presets panel — rendered only while a declared preset has no record — shows each preset's name, identifier, purpose, and its preflight result: the model the criteria currently match, or the requirement no configured model satisfies. One click imports a satisfiable preset through the existing endpoint. Imported records whose declaration changed since import carry a non-blocking "Preset changed" badge (checksum comparison; hand-created records that share a declared identifier are not flagged). The checksum-driven update flow remains future work (ADR-056, #357).
Translator usage rows get the right user
The specialized translator path bypasses the middleware pipeline, so the beUserUid a caller set on TranslationOptions never reached its usage tracking — translator rows always fell back to the ambient backend user. TranslationService now re-attaches the resolved uid to the options array it hands to TranslatorInterface implementations, and the DeepL and LLM translators forward it to trackUsage(). The key is pipeline metadata: implementations must not send it to the remote API, and the interface documentation says so. Speech and image services keep ambient attribution — their option shapes carry no budget fields (ADR-052, #358).
One exception class for validation errors
Nine remaining classes threw PHP's global InvalidArgumentException on input validation; they now throw nr_llm's Exception\InvalidArgumentException, so these errors are catchable through NrLlmExceptionInterface like the rest of the public surface. Backward compatible — the nr_llm class extends the SPL one, existing catches keep matching. TaskInputResolver keeps the global import: its only use is a catch around third-party code that can raise the plain SPL exception, which a subclass-narrowed catch would miss (ADR-053, #359).
Also in this release
The POST-and-reload boilerplate that backend list modules had each copied now lives in one shared ModuleAction.js helper, used by the configurations and tool-state modules; the other backend list modules still use the copied version. The 0.17.0 changelog was backfilled with the two Ollama entries (#341/#342) that had shipped without one (#356).
Pull requests
#356, #357, #358, #359, #360 — ADRs in Documentation/Adr/.
Full changelog: v0.17.0...v0.18.0