Added
-
⚡ Persistent probe-cache with TTL + auto-hide broken models (#144) — every health probe result is now cached to
~/.free-coding-models/probe-cache.jsonfor 24 hours, shared across all surfaces (CLI TUI, Web Dashboard / daemon, Tauri Desktop).- Warm start in <500ms: the full ranking renders from the cache instantly, then only models that are due (broken or TTL-expired) get re-pinged. Cold start with no cache behaves identically to before — no regression.
- Broken models stay hidden across sessions: a model that 401s today won't appear in tomorrow's default view. Recovery is automatic — if a previously-broken model comes back
ok, the next probe un-hides it. - Honors
XDG_CACHE_HOMEwhen set (Linux/macOS convention), else falls back to~/.free-coding-models/. File uses0600perms and is written atomically (tmp + rename) to survive crashes mid-flush. - Concurrency-safe: debounced flush + atomic rename + read-merge-write means a running daemon and an interactive CLI can share the file without clobbering each other. Worst case: one batch of deltas is lost, never the whole file.
probeVersionconstant (currently2): bump this when ping behaviour changes (new endpoint, new prompt, etc.) and the entire cache invalidates automatically — no manual purge.
-
🔘 Shift+B hotkey toggles visibility of probe-cache-broken models in the TUI. Footer chip shows
⚡ N cached · 🔴 M broken (Shift+B)(becomes🔴 M broken (visible)when toggled on). -
🛠️ New CLI flags:
--reprobe/--no-cache— force-rebuild the probe-cache this run (ping everything fresh).--probe-ttl <ms>— override the 24h TTL (e.g.--probe-ttl 3600000for 1h, useful when debugging model health).--show-broken— don't auto-hide broken models this run (one-shot override for--reprobestyle workflows).
-
📊 Daemon
/statsnow exposesprobeCachewithtotal,ok,broken,freshCount,staleCount,dueCount,hiddenCount,providers— the Web Dashboard renders it live so users can see cache hit rate at a glance.
Changed
-
🩺 Daemon health-probe loop (
runProbeBurst) now skips models that are fresh + ok in the persistent cache. Broken models naturally pass through, so recovery detection keeps working unchanged. This cuts daemon-side probe traffic by ~85% on warm starts. -
📁 Per-provider probe results are mirrored from the in-memory circuit-breaker windows into the persistent cache via
recordProbeResultinsrc/core/router-daemon.js. Debounced 2s flush avoids filesystem thrash during probe bursts.
Maintenance
- 🧪 +40 unit tests (
test/probe-cache.test.js, 36 → 76): covers freshness rules 1–5, path resolution (XDG_CACHE_HOME), corrupt-JSON recovery, version migration, multi-model fan-out,isCacheFreshper-condition checks,recordProbeResultsvalidation + garbage dropping,getCacheStatsaggregates,getCachedResultsForProviderfiltering,pruneStaleEntries, end-to-end reload, 4 concurrency tests (other-process deltas survive, stalelastProbedAtis overwritten, missing-file path, corrupt-file recovery). - 🧪 628 → 632 tests passing (
pnpm test), 107 → 118 suites. - 🧹
vite buildsucceeds. - 📖 README updated with a new 🧠 Persistent probe cache section (where the file lives, CLI flags, TUI keys, daemon
/statsshape).
Files
- New:
src/core/probe-cache.js(335 lines),test/probe-cache.test.js(332 lines),changelog/v0.5.58.md. - Modified:
src/tui/app.js(probe-cache load + apply + record),src/tui/key-handler.js(Shift+B handler),src/tui/render-table.js(footer chip),src/tui/tui-state.js(5 new state fields),src/tui/cli-help.js(3 new flag entries),src/core/utils.js(parseArgsgains--reprobe/--probe-ttl/--show-broken),src/core/router-daemon.js(full integration: load on boot, mirror probe results, skip fresh inrunProbeBurst, expose in/stats, flush on shutdown),README.md,tasks/t1.md,package.json(test script),changelog/.
Inspiration
This implementation is informed by apmantza/pi-free's lib/provider-probe.ts + lib/probe-cache.ts (which itself credits us: "Inspired by free-coding-models' extractQuotaPercent"). The inspiration flows both ways — we're reclaiming the lead on the probe-cache axis by going cross-surface (CLI / daemon / Tauri share one file) and concurrency-safe (read-merge-write vs pi-free's single-writer Pi-only model).