v0.5.12
Added
- Web Router Dashboard: full set manager. The M4 read-only "Model Health"
panel has been replaced with an interactive Active Set manager:- Add model picker with search + per-provider filter (147 routeable
models from the new/api/router/catalogendpoint). - Remove button per row.
- Move up / Move down buttons as a keyboard-accessible fallback.
- HTML5 drag-and-drop to reorder priorities. Visual drop indicator
(top/bottom border on the target row) shows where the row will land. - Active set switcher in the same header (when more than one set
exists). Switches the daemon'sactiveSetvia
POST /sets/:name/activate. - Save indicator (
saving… / ✓ saved / ⚠ error) shown next to the
Add button so the user always knows whether the last drag actually
persisted to disk.
- Add model picker with search + per-provider filter (147 routeable
- "Sync best models" button. One-click rebuild of the active set
using the probe-basedsync-setpipeline. Re-pings every candidate
with the user's actual API keys, picks only the models that come back
2xx with a reasonable latency, and persists the result. The Web UI
surfaces a "Synced N working models from M probes" toast with the
probe count. The button shows a "saving…" state while the probe runs
(up to ~60s for 16 candidates). - Daemon endpoints for granular set management.
POST /sets/:name/models— append a single model to a set.DELETE /sets/:name/models— remove a single model by
{ provider, model }.POST /sets/:name/reorder— accept a full priority order
{ order: ["provider/model", ...] }and re-number the set.POST /sets/:name/sync— re-runsync-setagainst the named set
withmaxProbes: 16, targetCount: 5so the Web UI never spends
more than ~60s on a sync.GET /api/router/catalog— lightweight catalog of routeable
models for the Add picker.
- Default router set is now probe-driven.
buildDefaultRouterSet
is async and accepts aprobeFnthat POSTs a 1-token chat completion
to each candidate. Models that come back 2xx with low latency are
pinned to the top of the set; failing models fall back to the static
tier ordering. A new user with a half-broken key set now gets a
default router set made of models that actually work, instead of a
hardcoded NVIDIA-only list that 401s for everyone without a
NIM-specific grant. - Web proxy for the new endpoints.
/api/router/sets/:name/models
(POST/DELETE),/api/router/sets/:name/reorder(POST),
/api/router/sets/:name/activate(POST),/api/router/sets/:name/sync
(POST, 180s proxy timeout),/api/router/catalog(GET, with a
fallback path that synthesizes the catalog fromsources.jswhen the
daemon is offline). - Switch case routing fix. The
if/regexblocks for parameterized
routes (/api/router/sets/:name/...) were previously inside the
request switch ascasestatements that never matched (JS switch
is exact-string equality). Moved them above the switch so they
actually run. This is why the original reorder/add/remove proxies
returned 404 in the v0.5.11 web build. - Auto-heal the active router set on startup. When the daemon starts,
it waits for the first probe burst to populate health data, then runs
autoHealActiveSet()to swap any broken model in the active set
(AUTH_ERRORorSTALEstate) for a working alternative. The picker
prefers the same provider first, then falls through to cross-provider
candidates, and skips providers whose every probe has been broken.
Three passes run at 8s, 24s, and 40s after startup so a freshly-added
replacement that turns out to be broken gets replaced again. The result:
a new user with a half-broken key set lands on a usable default set
by the time the Web Dashboard renders, and the Playground'sfcm
virtual model starts returning successful responses immediately. router.userCustomized+router.autoHealconfig flags. The first
manual edit to the active set (add / remove / reorder / sync /
activate / rename) flipsuserCustomizedtotrueandautoHeal
tofalseso the user's manual choices are never undone on the next
daemon start. New users getuserCustomized: falseandautoHeal: trueby default, which is what powers the M6 "default to working
models" promise./api/router/statusexposesautoHeal,userCustomized, and
brokenModelCount. The Web Router Dashboard reads these to surface
an amber banner when broken models remain in the active set, with a
one-click "Fix now" button that re-runs the samesync-setprobe
pipeline the CLI uses.- Amber "models not responding" banner in the Web Router Dashboard.
Shown when the daemon reportsbrokenModelCount > 0, with a
dismissable X so the user can ignore it after acknowledging the
problem. The "Fix now" button re-runs the probe-based heal and reloads
the dashboard state. - Unusable row fade (TUI + Web + Desktop). Rows whose health is
NO KEY(noauth) orAUTH FAIL(auth_error) are now rendered at
80% opacity (20% less opaque) on every user-facing surface. The user
can scan the table and instantly see which models they cannot
actually use, even when the cursor is parked on a different model.- TUI: the new
fadedRow()helper insrc/tui/render-helpers.js
multiplies every 24-bit RGB channel inside an ANSI-colored string by
0.8, so the whole line reads as uniformly faded. This works on
every terminal that supports truecolor and does not rely on the
SGR 2 "faint" code, which is ignored by some terminals. The fade
composes cleanly with the cursor highlight, the dark-red
incompatiblebackground, the greenrecommendedbackground, and
the goldfavoritebackground, so no existing visual cue is lost
— the "unusable" signal just rides on top of them. - Web / Desktop (Tauri): the
ModelTableadds an
.unusable { opacity: 0.8 }CSS class to rows whosem.statusis
noauthorauth_error. The class is held steady on hover so the
"you cannot use this" signal never disappears while the user is
inspecting the row.
- TUI: the new
fadedRow(input, factor = 0.8)helper. Pure function exported
fromsrc/tui/render-helpers.js, documented and unit-tested in
isolation. Identity fast-path forfactor >= 1, channels clamped to
0–255, bold/dim/reset SGR codes pass through unchanged. Reusable for
any future "fade a whole line" need (e.g. stale rows, soft-disabled
providers).- 6 new unit tests for the granular set-management endpoints
(add, duplicate, remove, reorder, reorder-with-missing-key,
catalog), 2 new tests forbuildDefaultRouterSet's probe path
(probe-preference + sync fallback), 4 new unit tests for the
auto-heal path (no-op when user-customized, no-op when auto-heal is
disabled, no-op when no broken models, and the
user-edit-flags-customization round-trip), and 12 new tests for
the unusable row fade (7 forfadedRow+ 5 for the renderTable
integration). All 515 tests pass.
Notes
- The TUI's
--sync-setflag is unchanged — the Web "Sync best" button
is the same pipeline with a smallermaxProbesso the UI stays
snappy. CLI users still get the full 50-probe budget. - The
fcmvirtual model that the Playground uses by default
automatically picks up the new working-models set, so the
Playground will start returning successful responses as soon as the
Web user clicks "Sync best" once. - The auto-heal is best-effort: if the user has only one working
provider, the healed set will shrink to that one provider's top
model. That's still better than the previous behavior of showing
three models that all 401. - The new broken-model banner is intentionally subtle (amber, not red)
because the auto-heal already does its best to recover. It's there
for the "user has 0 working keys" edge case so the user can click
through to "Fix now" / "Sync best" and either get a working set or
see the toast explaining the situation. - The cursor row is still faded if the model is unusable — the user's
request was "the WHOLE line at 80% opacity" and we honor that
literally. The cursor highlight (blue background) gets its colors
multiplied by 0.8 too, so it remains visible but reads as "dimmed",
consistent with the rest of the line.
How the auto-heal picker works (M6)
For each broken model in the active set:
1. Same provider — pick a working model of the same provider
(skipping models that the circuit breaker already knows are broken).
2. Cross-provider — fall through to any working model across all
providers, sorted by static tier + swe score.
3. If neither yields a working model, leave the broken entry in
place and log a warning (the Web UI surfaces this in the
"models not responding" banner).