free-coding-models 0.5.57 → 0.5.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/changelog/v0.5.58.md +42 -0
- package/changelog/v0.5.59.md +52 -0
- package/package.json +4 -4
- package/src/core/ping.js +7 -2
- package/src/core/probe-cache.js +515 -0
- package/src/core/provider-quota-fetchers.js +254 -2
- package/src/core/router-daemon.js +81 -4
- package/src/core/utils.js +24 -1
- package/src/tui/app.js +132 -4
- package/src/tui/cli-help.js +3 -0
- package/src/tui/key-handler.js +19 -0
- package/src/tui/render-table.js +50 -2
- package/src/tui/tui-state.js +11 -0
- package/web/dist/assets/{index-BoJ4r2gC.js → index-C_ZdUGrS.js} +2 -2
- package/web/dist/assets/{index-BkK1gdJN.css → index-wI9xrm0w.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/src/components/router/RouterView.jsx +34 -0
- package/web/src/components/router/RouterView.module.css +53 -0
package/src/tui/app.js
CHANGED
|
@@ -99,7 +99,7 @@ import { buildMergedModels } from '../core/model-merger.js'
|
|
|
99
99
|
import { loadOpenCodeConfig, saveOpenCodeConfig } from '../core/opencode-config.js'
|
|
100
100
|
import { usageForRow as _usageForRow } from '../core/usage-reader.js'
|
|
101
101
|
import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../core/token-usage-reader.js'
|
|
102
|
-
import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../core/provider-quota-fetchers.js'
|
|
102
|
+
import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule, getAllQuotas as _getAllPassiveQuotasFromModule } from '../core/provider-quota-fetchers.js'
|
|
103
103
|
import { isKnownQuotaTelemetry } from '../core/quota-capabilities.js'
|
|
104
104
|
import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../core/constants.js'
|
|
105
105
|
import { TIER_COLOR } from './tier-colors.js'
|
|
@@ -128,6 +128,17 @@ import { startExternalTool } from '../core/tool-launchers.js'
|
|
|
128
128
|
import { getToolInstallPlan, installToolWithPlan, isToolInstalled } from '../core/tool-bootstrap.js'
|
|
129
129
|
import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../core/endpoint-installer.js'
|
|
130
130
|
import { loadCache, saveCache, clearCache, getCacheAge } from '../core/cache.js'
|
|
131
|
+
import {
|
|
132
|
+
loadCache as loadProbeCache,
|
|
133
|
+
flushCache as flushProbeCache,
|
|
134
|
+
clearCache as clearProbeCache,
|
|
135
|
+
getModelsDueForProbe,
|
|
136
|
+
isCacheFresh,
|
|
137
|
+
recordProbeResults,
|
|
138
|
+
getCacheStats as getProbeCacheStats,
|
|
139
|
+
pruneStaleEntries as pruneProbeCacheStaleEntries,
|
|
140
|
+
DEFAULT_PROBE_TTL_MS,
|
|
141
|
+
} from '../core/probe-cache.js'
|
|
131
142
|
import { checkConfigSecurity } from '../core/security.js'
|
|
132
143
|
import { buildCliHelpText } from './cli-help.js'
|
|
133
144
|
import { detectActiveTheme, THEME_BG_RGB, getTheme, patchThemeBg } from './theme.js'
|
|
@@ -424,6 +435,78 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
424
435
|
}
|
|
425
436
|
}
|
|
426
437
|
|
|
438
|
+
// 📖 Probe-cache (t1): persistent per-provider health cache with TTL + auto-hide broken.
|
|
439
|
+
// 📖 Lives at ~/.free-coding-models/probe-cache.json, shared with daemon + Tauri surfaces.
|
|
440
|
+
// 📖 Honors CLI flags: --reprobe (force clear), --probe-ttl <ms>, --show-broken (don't hide).
|
|
441
|
+
if (cliArgs.reprobeMode) {
|
|
442
|
+
// 📖 --reprobe / --no-cache: nuke the cache so this run pings everything fresh.
|
|
443
|
+
clearProbeCache()
|
|
444
|
+
}
|
|
445
|
+
const probeCacheTtlMs = cliArgs.probeTtlMs ?? DEFAULT_PROBE_TTL_MS
|
|
446
|
+
state.probeCacheTtlMs = probeCacheTtlMs
|
|
447
|
+
state.showBrokenMode = !!cliArgs.showBrokenMode
|
|
448
|
+
const probeCache = loadProbeCache()
|
|
449
|
+
|
|
450
|
+
// 📖 Prune entries whose modelId is no longer in the catalog (one-time per boot).
|
|
451
|
+
for (const providerKey of new Set(state.results.map(r => r.providerKey))) {
|
|
452
|
+
const liveIds = state.results.filter(r => r.providerKey === providerKey).map(r => r.modelId)
|
|
453
|
+
pruneProbeCacheStaleEntries(providerKey, liveIds)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 📖 Apply probe-cache to results: auto-hide broken, pre-fill fresh ok stats.
|
|
457
|
+
let probeCacheHits = 0
|
|
458
|
+
let probeCacheMisses = 0
|
|
459
|
+
for (const r of state.results) {
|
|
460
|
+
const entry = probeCache?.providers?.[r.providerKey]?.models?.[r.modelId]
|
|
461
|
+
if (!entry) {
|
|
462
|
+
probeCacheMisses++
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
if (entry.status === 'broken') {
|
|
466
|
+
probeCacheHits++
|
|
467
|
+
r.cachedBroken = true
|
|
468
|
+
r.lastProbedAt = entry.lastProbedAt
|
|
469
|
+
r.lastError = entry.lastError ?? null
|
|
470
|
+
// 📖 Auto-hide broken models (unless --show-broken or feature disabled).
|
|
471
|
+
const autoHideEnabled = config.settings?.autoHideBrokenModels !== false
|
|
472
|
+
if (!state.showBrokenMode && autoHideEnabled) {
|
|
473
|
+
r.hidden = true
|
|
474
|
+
r.status = 'down'
|
|
475
|
+
}
|
|
476
|
+
continue
|
|
477
|
+
}
|
|
478
|
+
if (entry.status !== 'ok') {
|
|
479
|
+
probeCacheMisses++
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
if (entry.probeVersion !== 2) {
|
|
483
|
+
// 📖 Stale version — count as miss, force re-probe this run.
|
|
484
|
+
probeCacheMisses++
|
|
485
|
+
continue
|
|
486
|
+
}
|
|
487
|
+
if (Date.now() - entry.lastProbedAt < probeCacheTtlMs) {
|
|
488
|
+
// 📖 Fresh ok entry — pre-fill the row so the TUI has data on frame 1.
|
|
489
|
+
probeCacheHits++
|
|
490
|
+
r.fromProbeCache = true
|
|
491
|
+
r.lastProbedAt = entry.lastProbedAt
|
|
492
|
+
const cachedLatency = typeof entry.latencyMs === 'number' ? entry.latencyMs : 0
|
|
493
|
+
r.avg = cachedLatency
|
|
494
|
+
r.p95 = cachedLatency
|
|
495
|
+
r.jitter = 0
|
|
496
|
+
r.stability = 100
|
|
497
|
+
r.uptime = 100
|
|
498
|
+
r.verdict = 'Cached'
|
|
499
|
+
r.status = 'up'
|
|
500
|
+
r.httpCode = '200'
|
|
501
|
+
r.pings = [{ ms: cachedLatency, code: '200' }]
|
|
502
|
+
} else {
|
|
503
|
+
probeCacheMisses++
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
state.probeCacheHits = probeCacheHits
|
|
507
|
+
state.probeCacheMisses = probeCacheMisses
|
|
508
|
+
state.probeCacheBrokenHidden = state.results.filter(r => r.cachedBroken && r.hidden).length
|
|
509
|
+
|
|
427
510
|
// 📖 Define pingModel before JSON mode so `--json` can reuse the same provider-aware
|
|
428
511
|
// 📖 ping path as the interactive TUI without waiting for the PTY/render loop setup.
|
|
429
512
|
pingModel = async (r) => {
|
|
@@ -445,6 +528,26 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
445
528
|
|
|
446
529
|
r.pings.push({ ms, code })
|
|
447
530
|
|
|
531
|
+
// 📖 Probe-cache (t1): record the result so future runs can skip healthy models
|
|
532
|
+
// 📖 and auto-hide broken ones. Uses the module-level in-memory cache; flushed
|
|
533
|
+
// 📖 on exit (see exit()) and on graceful shutdown.
|
|
534
|
+
const probeStatus = code === '200' ? 'ok' : 'broken'
|
|
535
|
+
recordProbeResults(r.providerKey, [{
|
|
536
|
+
modelId: r.modelId,
|
|
537
|
+
status: probeStatus,
|
|
538
|
+
latencyMs: typeof ms === 'number' ? ms : null,
|
|
539
|
+
lastError: probeStatus === 'broken' ? String(code) : null,
|
|
540
|
+
}])
|
|
541
|
+
// 📖 If a previously-broken model comes back ok, un-hide it automatically so the
|
|
542
|
+
// 📖 user sees the recovery without a restart. (Skip when --show-broken keeps them visible.)
|
|
543
|
+
if (probeStatus === 'ok' && r.cachedBroken && !state.showBrokenMode) {
|
|
544
|
+
r.cachedBroken = false
|
|
545
|
+
r.hidden = false
|
|
546
|
+
r.lastError = null
|
|
547
|
+
// 📖 Re-evaluate the broken-hidden counter for the footer chip.
|
|
548
|
+
state.probeCacheBrokenHidden = state.results.filter(x => x.cachedBroken && x.hidden).length
|
|
549
|
+
}
|
|
550
|
+
|
|
448
551
|
if (code === '200') {
|
|
449
552
|
r.status = 'up'
|
|
450
553
|
} else if (code === '000') {
|
|
@@ -541,6 +644,7 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
541
644
|
// 📖 Ensure we always leave alt screen cleanly (Ctrl+C, crash, normal exit)
|
|
542
645
|
const exit = (code = 0) => {
|
|
543
646
|
saveCache(state.results, state.pingMode)
|
|
647
|
+
flushProbeCache()
|
|
544
648
|
clearInterval(ticker)
|
|
545
649
|
clearTimeout(state.pingIntervalObj)
|
|
546
650
|
clearInterval(state.versionRecheckTimer)
|
|
@@ -845,6 +949,15 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
845
949
|
probeTotal: state.probeTotal,
|
|
846
950
|
probeCompleted: state.probeCompleted,
|
|
847
951
|
probeHiddenCount: state.probeHiddenCount,
|
|
952
|
+
// 📖 Probe-cache (t1): footer chip stats
|
|
953
|
+
probeCacheHits: state.probeCacheHits || 0,
|
|
954
|
+
probeCacheMisses: state.probeCacheMisses || 0,
|
|
955
|
+
probeCacheBrokenHidden: state.probeCacheBrokenHidden || 0,
|
|
956
|
+
showBrokenMode: !!state.showBrokenMode,
|
|
957
|
+
// 📖 Passive quota (t2): live snapshots from response headers, populated
|
|
958
|
+
// 📖 by pings (every ping pre-warms quota for its provider). See
|
|
959
|
+
// 📖 src/core/provider-quota-fetchers.js for getAllPassiveQuotas().
|
|
960
|
+
quota: Object.fromEntries(_getAllPassiveQuotasFromModule()),
|
|
848
961
|
}
|
|
849
962
|
if (state.commandPaletteOpen) {
|
|
850
963
|
if (!state.commandPaletteFrozenTable) {
|
|
@@ -970,8 +1083,13 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
970
1083
|
|
|
971
1084
|
// ── Continuous ping loop — ping all models every N seconds forever ──────────
|
|
972
1085
|
|
|
973
|
-
// 📖 Initial ping
|
|
974
|
-
|
|
1086
|
+
// 📖 Initial ping: skip models that are still fresh in the probe-cache (t1).
|
|
1087
|
+
// 📖 They already have valid data pre-filled and will be re-probed when their TTL expires.
|
|
1088
|
+
// 📖 Broken models are always re-probed so recovery is detected.
|
|
1089
|
+
const initialDueModels = state.results.filter(r => !isCacheFresh(r.providerKey, r.modelId, {
|
|
1090
|
+
ttlMs: state.probeCacheTtlMs,
|
|
1091
|
+
}))
|
|
1092
|
+
const initialPing = Promise.all(initialDueModels.map(r => pingModel(r)))
|
|
975
1093
|
|
|
976
1094
|
// 📖 Continuous ping loop with mode-driven cadence.
|
|
977
1095
|
const runPingCycle = async () => {
|
|
@@ -1008,6 +1126,13 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
1008
1126
|
if (!state.config.favorites.includes(favKey)) return
|
|
1009
1127
|
}
|
|
1010
1128
|
|
|
1129
|
+
// 📖 Probe-cache (t1): skip models that are still fresh + ok in the cache.
|
|
1130
|
+
// 📖 Broken models always pass through isCacheFresh() (it returns false for them),
|
|
1131
|
+
// 📖 which gives us automatic recovery detection for free.
|
|
1132
|
+
if (isCacheFresh(r.providerKey, r.modelId, { ttlMs: state.probeCacheTtlMs })) {
|
|
1133
|
+
return
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1011
1136
|
pingModel(r).catch(() => {
|
|
1012
1137
|
// Individual ping failures don't crash the loop
|
|
1013
1138
|
})
|
|
@@ -1031,8 +1156,11 @@ export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
|
1031
1156
|
await initialPing
|
|
1032
1157
|
scheduleAiSpeedScanOnStartup()
|
|
1033
1158
|
|
|
1034
|
-
// 📖 Save
|
|
1159
|
+
// 📖 Save caches after initial pings complete for faster next startup.
|
|
1160
|
+
// 📖 Both caches live side-by-side: the old per-session cache for instant frame-1
|
|
1161
|
+
// 📖 data, and the new persistent probe-cache for cross-session skip + auto-hide.
|
|
1035
1162
|
saveCache(state.results, state.pingMode)
|
|
1163
|
+
flushProbeCache()
|
|
1036
1164
|
|
|
1037
1165
|
// 📖 Background version re-check: poll npm registry every 5 minutes.
|
|
1038
1166
|
// 📖 If a new version appears (wasn't there at startup), update the banner live.
|
package/src/tui/cli-help.js
CHANGED
|
@@ -33,6 +33,9 @@ const ANALYSIS_FLAGS = [
|
|
|
33
33
|
{ flag: '--ping-interval <ms>', description: 'Override ping interval in milliseconds' },
|
|
34
34
|
{ flag: '--hide-unconfigured', description: 'Hide models without configured API keys' },
|
|
35
35
|
{ flag: '--show-unconfigured', description: 'Show all models regardless of API key config' },
|
|
36
|
+
{ flag: '--reprobe, --no-cache', description: 'Force-rebuild the persistent probe-cache this run (skip cached healthy models)' },
|
|
37
|
+
{ flag: '--probe-ttl <ms>', description: 'Override probe-cache TTL (default 24h); healthy models within TTL are not re-pinged' },
|
|
38
|
+
{ flag: '--show-broken', description: "Don't auto-hide models that the probe-cache marked broken (one-shot override)" },
|
|
36
39
|
]
|
|
37
40
|
|
|
38
41
|
const CONFIG_FLAGS = [
|
package/src/tui/key-handler.js
CHANGED
|
@@ -2784,6 +2784,25 @@ export function createKeyHandler(ctx) {
|
|
|
2784
2784
|
return
|
|
2785
2785
|
}
|
|
2786
2786
|
|
|
2787
|
+
// 📖 Shift+B: Toggle visibility of probe-cache-broken models (t1).
|
|
2788
|
+
// 📖 When toggled on, hidden broken rows become visible (greyed/dimmed via render-table).
|
|
2789
|
+
// 📖 When toggled off, they go back to hidden. Persisted for the current session.
|
|
2790
|
+
if (key.name === 'b' && key.shift && !key.ctrl && !key.meta) {
|
|
2791
|
+
const nextShowBroken = !state.showBrokenMode
|
|
2792
|
+
state.showBrokenMode = nextShowBroken
|
|
2793
|
+
// 📖 If enabling, restore all broken rows to visible; if disabling, re-hide them.
|
|
2794
|
+
for (const r of state.results) {
|
|
2795
|
+
if (r.cachedBroken) {
|
|
2796
|
+
r.hidden = !nextShowBroken
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
// 📖 Refresh the footer counter chip + cursor bounds.
|
|
2800
|
+
state.probeCacheBrokenHidden = state.results.filter(x => x.cachedBroken && x.hidden).length
|
|
2801
|
+
applyTierFilter()
|
|
2802
|
+
refreshVisibleSorted({ resetCursor: false })
|
|
2803
|
+
return
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2787
2806
|
// 📖 X clears the active free-text filter set from the command palette.
|
|
2788
2807
|
if (key.name === 'x' && !key.ctrl && !key.meta) {
|
|
2789
2808
|
if (!state.customTextFilter) return
|
package/src/tui/render-table.js
CHANGED
|
@@ -151,6 +151,14 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
151
151
|
* routerFooterTodayTokens?: number,
|
|
152
152
|
* routerFooterAllTimeTokens?: number,
|
|
153
153
|
* routerFooterRequests?: number,
|
|
154
|
+
* probeCacheHits?: number, // t1: number of fresh ok entries served from probe-cache
|
|
155
|
+
* probeCacheMisses?: number, // t1: number of models that needed a live probe
|
|
156
|
+
* probeCacheBrokenHidden?: number, // t1: number of broken models auto-hidden this session
|
|
157
|
+
* showBrokenMode?: boolean, // t1: true when Shift+B has un-hidden broken models
|
|
158
|
+
* quota?: Record<string, { // t2: live quota from response headers
|
|
159
|
+
* remaining: number, limit: number, percent: number,
|
|
160
|
+
* source: 'header'|'endpoint', lastUpdated: number, windowType?: string,
|
|
161
|
+
* }>,
|
|
154
162
|
* }} opts
|
|
155
163
|
* @returns {string}
|
|
156
164
|
*/
|
|
@@ -197,6 +205,11 @@ export function renderTable({
|
|
|
197
205
|
probeTotal = 0,
|
|
198
206
|
probeCompleted = 0,
|
|
199
207
|
probeHiddenCount = 0,
|
|
208
|
+
probeCacheHits = 0,
|
|
209
|
+
probeCacheMisses = 0,
|
|
210
|
+
probeCacheBrokenHidden = 0,
|
|
211
|
+
showBrokenMode = false,
|
|
212
|
+
quota = {},
|
|
200
213
|
} = _) {
|
|
201
214
|
// 📖 Filter out hidden models for display
|
|
202
215
|
const visibleResults = results.filter(r => !r.hidden)
|
|
@@ -1199,8 +1212,39 @@ export function renderTable({
|
|
|
1199
1212
|
probeLabel = chalk.bgRgb(120, 60, 60).rgb(255, 200, 200).bold(` 🔍 Probe done: ${probeHiddenCount} broken model${probeHiddenCount > 1 ? 's' : ''} hidden `)
|
|
1200
1213
|
}
|
|
1201
1214
|
|
|
1202
|
-
// 📖
|
|
1203
|
-
|
|
1215
|
+
// 📖 Probe-cache chip (t1): `⚡ N cached · 🔴 M broken (Shift+B)`
|
|
1216
|
+
// 📖 Always shown once any probe-cache state exists — gives the user a hint that
|
|
1217
|
+
// 📖 cache hits are happening (and that broken models are being hidden for them).
|
|
1218
|
+
let probeCacheLabel = ''
|
|
1219
|
+
if (probeCacheHits > 0 || probeCacheBrokenHidden > 0 || showBrokenMode) {
|
|
1220
|
+
const cachedTxt = `⚡ ${probeCacheHits} cached`
|
|
1221
|
+
const brokenTxt = probeCacheBrokenHidden > 0
|
|
1222
|
+
? ` 🔴 ${probeCacheBrokenHidden} broken${showBrokenMode ? ' (visible)' : ' (Shift+B)'}`
|
|
1223
|
+
: ''
|
|
1224
|
+
probeCacheLabel = chalk.bgRgb(40, 90, 140).rgb(220, 235, 255).bold(` ${cachedTxt}${brokenTxt} `)
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// 📖 Passive quota chip (t2): `📊 groq 78% · sambanova 41%` (top-N depleted first).
|
|
1228
|
+
// 📖 Shows live rate-limit headers that the daemon / pings collected from upstream
|
|
1229
|
+
// 📖 responses. Zero extra network requests — see provider-quota-fetchers.js.
|
|
1230
|
+
// 📖 Cap at 5 providers to avoid footer bloat; pick most-depleted first.
|
|
1231
|
+
let quotaLabel = ''
|
|
1232
|
+
if (quota && typeof quota === 'object') {
|
|
1233
|
+
const entries = Object.entries(quota)
|
|
1234
|
+
.filter(([, s]) => s && typeof s.percent === 'number')
|
|
1235
|
+
.sort((a, b) => a[1].percent - b[1].percent) // 📖 most depleted first
|
|
1236
|
+
.slice(0, 5)
|
|
1237
|
+
if (entries.length > 0) {
|
|
1238
|
+
const parts = entries.map(([providerKey, snap]) => {
|
|
1239
|
+
const icon = snap.percent <= 10 ? '🚨' : snap.percent <= 25 ? '⚠️ ' : '📊'
|
|
1240
|
+
return `${icon} ${providerKey} ${snap.percent}%`
|
|
1241
|
+
})
|
|
1242
|
+
quotaLabel = chalk.bgRgb(60, 100, 60).rgb(220, 255, 220).bold(` ${parts.join(' · ')} `)
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// 📖 Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Quota + Last release
|
|
1247
|
+
if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel || quotaLabel) {
|
|
1204
1248
|
const parts = [
|
|
1205
1249
|
{ text: ' ', key: null },
|
|
1206
1250
|
{ text: speedTestLabel, key: 'a' },
|
|
@@ -1208,6 +1252,10 @@ export function renderTable({
|
|
|
1208
1252
|
{ text: globalBenchmarkLabel, key: 'u' },
|
|
1209
1253
|
{ text: probeLabel ? ' ' : '', key: null },
|
|
1210
1254
|
{ text: probeLabel, key: null },
|
|
1255
|
+
{ text: probeCacheLabel ? ' ' : '', key: null },
|
|
1256
|
+
{ text: probeCacheLabel, key: null },
|
|
1257
|
+
{ text: quotaLabel ? ' ' : '', key: null },
|
|
1258
|
+
{ text: quotaLabel, key: null },
|
|
1211
1259
|
{ text: ' ', key: null },
|
|
1212
1260
|
{ text: releaseLabel, key: null },
|
|
1213
1261
|
]
|
package/src/tui/tui-state.js
CHANGED
|
@@ -286,6 +286,17 @@ export function createTuiState({
|
|
|
286
286
|
probeCompleted: 0,
|
|
287
287
|
probeHiddenCount: 0,
|
|
288
288
|
|
|
289
|
+
// 📖 Persistent probe-cache (t1): TTL'd cross-session health cache.
|
|
290
|
+
// 📖 - probeCacheHits / Misses: telemetry counters per session (footer chip).
|
|
291
|
+
// 📖 - probeCacheBrokenHidden: live count of broken rows currently hidden.
|
|
292
|
+
// 📖 - probeCacheTtlMs: TTL override from --probe-ttl CLI flag (default 24h).
|
|
293
|
+
// 📖 - showBrokenMode: Shift+B toggle; true = broken rows are visible (dimmed).
|
|
294
|
+
probeCacheHits: 0,
|
|
295
|
+
probeCacheMisses: 0,
|
|
296
|
+
probeCacheBrokenHidden: 0,
|
|
297
|
+
probeCacheTtlMs: null, // 📖 Set in app.js from cliArgs.probeTtlMs || DEFAULT
|
|
298
|
+
showBrokenMode: false,
|
|
299
|
+
|
|
289
300
|
// 📖 Header click flash animation: briefly highlights the clicked column header
|
|
290
301
|
// 📖 with an inverse/bright style for ~250ms (3 frames at 12 FPS).
|
|
291
302
|
headerFlashColumn: null, // 📖 Column name being flashed (null = no flash active)
|