Recover model-usage limits from a boot-time probe failure
The shell starts before DHCP finishes, so Claude's first probe of the OAuth usage endpoint came back as a transport failure and pinned "Claude limits unavailable" for a full refresh interval. Opening the panel could not clear it either: the open path refreshed unforced and hit the same throttle it was trying to escape. A transport failure now arms a short retry that goes straight to the probe, since a retry answering to the throttle never gets off the ground. Any real answer disarms it — a server that replied, 429 included, is one to stop pestering. Probes are single-flight, a fresh token skips the throttle its predecessor set, and opening the panel asks for the wire without re-walking every transcript on disk. The module also earns its place in the bar now instead of sitting there dimmed with nothing to say: it collapses out entirely until a provider has actually recorded usage, here or on a synced machine. That made both presence probes redundant, so they are gone. Selection follows the provider rather than its slot, so one appearing while the panel is open no longer swaps out what you were reading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9b693cca63
commit
24dc914d28
@@ -27,18 +27,33 @@ Item {
|
|||||||
|
|
||||||
property var providers: [claudeProvider, codexProvider]
|
property var providers: [claudeProvider, codexProvider]
|
||||||
|
|
||||||
// A subscription earns a place in the panel by being both switched on in
|
// A subscription earns a place in the bar and the panel by being switched on
|
||||||
// settings and actually present on this machine — nobody wants a Codex tab
|
// in settings and having actually produced numbers — locally or on a synced
|
||||||
// full of zeroes on a box that has never run Codex.
|
// device. Presence on disk is not enough: a box that installed Codex and
|
||||||
|
// never ran it would get a tab full of zeroes. With nothing to show, the
|
||||||
|
// whole module collapses out of the bar rather than sitting there dimmed.
|
||||||
property var enabledProviders: {
|
property var enabledProviders: {
|
||||||
var rev = syncRevision
|
var rev = syncRevision
|
||||||
var running = syncRunning
|
var running = syncRunning
|
||||||
var result = []
|
var result = []
|
||||||
if (claudeProvider.enabled && claudeProvider.installed) result.push(displayProvider(claudeProvider))
|
if (claudeProvider.enabled) {
|
||||||
if (codexProvider.enabled && codexProvider.installed) result.push(displayProvider(codexProvider))
|
var claude = displayProvider(claudeProvider)
|
||||||
|
if (providerHasData(claude)) result.push(claude)
|
||||||
|
}
|
||||||
|
if (codexProvider.enabled) {
|
||||||
|
var codex = displayProvider(codexProvider)
|
||||||
|
if (providerHasData(codex)) result.push(codex)
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All-time, not today: a quiet day is not the same as an absent provider.
|
||||||
|
function providerHasData(p) {
|
||||||
|
return numberValue(p.totalPrompts) > 0 || numberValue(p.totalSessions) > 0
|
||||||
|
|| numberValue(p.activeDays) > 0 || Number(p.rateLimitPercent) >= 0
|
||||||
|
|| Number(p.secondaryRateLimitPercent) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
property bool refreshing: claudeProvider.refreshing || codexProvider.refreshing || syncRunning
|
property bool refreshing: claudeProvider.refreshing || codexProvider.refreshing || syncRunning
|
||||||
property double aggregateUpdatedAtMs: aggregateData && aggregateData.updatedAtMs ? Number(aggregateData.updatedAtMs) : 0
|
property double aggregateUpdatedAtMs: aggregateData && aggregateData.updatedAtMs ? Number(aggregateData.updatedAtMs) : 0
|
||||||
property double lastRefreshedAtMs: Math.max(aggregateUpdatedAtMs, claudeProvider.lastRefreshedAtMs || 0, codexProvider.lastRefreshedAtMs || 0)
|
property double lastRefreshedAtMs: Math.max(aggregateUpdatedAtMs, claudeProvider.lastRefreshedAtMs || 0, codexProvider.lastRefreshedAtMs || 0)
|
||||||
@@ -523,6 +538,16 @@ Item {
|
|||||||
scheduleSync()
|
scheduleSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Opening the panel wants the numbers that go stale on the wire, not another
|
||||||
|
// walk over every transcript on disk. Forcing a whole refresh would do both,
|
||||||
|
// and re-opening the panel would then rescan the lot each time.
|
||||||
|
function refreshLimits() {
|
||||||
|
for (var i = 0; i < providers.length; i++) {
|
||||||
|
var p = providers[i]
|
||||||
|
if (p.enabled && typeof p.refreshLimits === "function") p.refreshLimits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatTokenCount(n) {
|
function formatTokenCount(n) {
|
||||||
if (n === undefined || n === null) return "0"
|
if (n === undefined || n === null) return "0"
|
||||||
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B"
|
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B"
|
||||||
|
|||||||
@@ -19,10 +19,16 @@ Panel {
|
|||||||
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||||
|
|
||||||
readonly property var providers: usage.enabledProviders
|
readonly property var providers: usage.enabledProviders
|
||||||
property int providerIndex: 0
|
// The selection follows the provider, not the slot it happens to sit in: a
|
||||||
readonly property var provider: providers.length > 0
|
// provider whose first scan lands while the panel is open would otherwise
|
||||||
? providers[Math.max(0, Math.min(providerIndex, providers.length - 1))]
|
// shift the list underneath you and swap out what you were reading.
|
||||||
: null
|
property string selectedProviderId: ""
|
||||||
|
readonly property int providerIndex: {
|
||||||
|
for (var i = 0; i < providers.length; i++)
|
||||||
|
if (providers[i].providerId === selectedProviderId) return i
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
readonly property var provider: providers.length > 0 ? providers[providerIndex] : null
|
||||||
|
|
||||||
property bool cursorActive: false
|
property bool cursorActive: false
|
||||||
|
|
||||||
@@ -39,11 +45,9 @@ Panel {
|
|||||||
function alpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a) }
|
function alpha(c, a) { return Qt.rgba(c.r, c.g, c.b, a) }
|
||||||
|
|
||||||
function selectProvider(index) {
|
function selectProvider(index) {
|
||||||
if (providers.length === 0) {
|
if (providers.length === 0) return
|
||||||
providerIndex = 0
|
var wrapped = ((index % providers.length) + providers.length) % providers.length
|
||||||
return
|
selectedProviderId = providers[wrapped].providerId
|
||||||
}
|
|
||||||
providerIndex = ((index % providers.length) + providers.length) % providers.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshNow() {
|
function refreshNow() {
|
||||||
@@ -317,16 +321,20 @@ Panel {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nothing to report, nothing in the bar: Bar.qml collapses a slot whose item
|
||||||
|
// is invisible, so the icon appears the moment the first scan finds usage and
|
||||||
|
// stays away entirely on a machine that has never run either CLI.
|
||||||
|
visible: providers.length > 0
|
||||||
implicitWidth: button.implicitWidth
|
implicitWidth: button.implicitWidth
|
||||||
implicitHeight: button.implicitHeight
|
implicitHeight: button.implicitHeight
|
||||||
|
|
||||||
onProvidersChanged: if (providerIndex >= providers.length) selectProvider(0)
|
|
||||||
onProviderIndexChanged: if (panelFlick) panelFlick.contentY = 0
|
onProviderIndexChanged: if (panelFlick) panelFlick.contentY = 0
|
||||||
onOpenedChanged: if (opened) {
|
onOpenedChanged: if (opened) {
|
||||||
cursorActive = false
|
cursorActive = false
|
||||||
nowMs = Date.now()
|
nowMs = Date.now()
|
||||||
if (panelFlick) panelFlick.contentY = 0
|
if (panelFlick) panelFlick.contentY = 0
|
||||||
usage.refreshAll()
|
usage.refreshAll()
|
||||||
|
usage.refreshLimits()
|
||||||
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +369,6 @@ Panel {
|
|||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: ""
|
text: ""
|
||||||
active: root.alarming
|
active: root.alarming
|
||||||
dimmed: root.providers.length === 0
|
|
||||||
onPressed: function(buttonCode) {
|
onPressed: function(buttonCode) {
|
||||||
if (buttonCode === Qt.RightButton) root.refreshNow()
|
if (buttonCode === Qt.RightButton) root.refreshNow()
|
||||||
else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1)
|
else if (buttonCode === Qt.MiddleButton) root.selectProvider(root.providerIndex + 1)
|
||||||
@@ -441,7 +448,7 @@ Panel {
|
|||||||
visible: root.providers.length === 0
|
visible: root.providers.length === 0
|
||||||
width: parent.width
|
width: parent.width
|
||||||
topPadding: Style.space(24)
|
topPadding: Style.space(24)
|
||||||
text: "No AI coding subscriptions found.\nClaude Code and Codex show up here once installed."
|
text: "No AI coding subscriptions found.\nClaude Code and Codex show up here once you've used them."
|
||||||
color: root.dim
|
color: root.dim
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ adapter per subscription.
|
|||||||
tokens per model with the bar behind each row showing its share of the
|
tokens per model with the bar behind each row showing its share of the
|
||||||
heaviest one. Hover for the input / output / cache split.
|
heaviest one. Hover for the input / output / cache split.
|
||||||
|
|
||||||
A subscription appears only when it is both enabled in settings and actually
|
A subscription appears only when it is enabled in settings and has actually
|
||||||
present on this machine (state directory or CLI). With one installed there is
|
recorded usage — on this machine or on a synced one. With one such provider
|
||||||
no switch row at all; with none, the panel says so and the bar icon dims.
|
there is no switch row at all; with none, the module leaves the bar entirely
|
||||||
|
rather than sitting there with nothing to say. A CLI installed mid-session
|
||||||
|
shows up at the next refresh, so nothing polls the disk waiting for it.
|
||||||
|
|
||||||
## Providers
|
## Providers
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ Item {
|
|||||||
property string tierLabel: ""
|
property string tierLabel: ""
|
||||||
property string authHelpText: "Run `claude auth login` to restore authoritative usage."
|
property string authHelpText: "Run `claude auth login` to restore authoritative usage."
|
||||||
property bool hasLocalStats: true
|
property bool hasLocalStats: true
|
||||||
// Optimistic until the probe answers, so a present provider never blinks
|
|
||||||
// out of the panel on startup.
|
|
||||||
property bool installed: true
|
|
||||||
property bool hasProjectStats: false
|
property bool hasProjectStats: false
|
||||||
|
|
||||||
property string oauthAccessToken: ""
|
property string oauthAccessToken: ""
|
||||||
@@ -50,8 +47,14 @@ Item {
|
|||||||
property string rateLimitTier: ""
|
property string rateLimitTier: ""
|
||||||
property bool hasAuthoritativeRateLimit: false
|
property bool hasAuthoritativeRateLimit: false
|
||||||
|
|
||||||
|
property bool probeInFlight: false
|
||||||
property double lastProbeAtMs: 0
|
property double lastProbeAtMs: 0
|
||||||
property int probeMinIntervalMs: 15 * 60 * 1000
|
property int probeMinIntervalMs: 15 * 60 * 1000
|
||||||
|
// Opening the panel asks for fresh limits, so a forced probe skips the
|
||||||
|
// background interval — but not so freely that flicking the panel open and
|
||||||
|
// shut turns into a request per flick.
|
||||||
|
property int probeForcedMinIntervalMs: 15 * 1000
|
||||||
|
property int probeRetryMs: 30 * 1000
|
||||||
property bool projectScanRerunForce: false
|
property bool projectScanRerunForce: false
|
||||||
|
|
||||||
property var providerSettings: ({})
|
property var providerSettings: ({})
|
||||||
@@ -104,17 +107,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claude Code is "here" if its state directory or CLI exists. Cheap enough
|
|
||||||
// to re-run on every refresh, so installing it mid-session shows up.
|
|
||||||
Process {
|
|
||||||
id: presenceProbe
|
|
||||||
running: true
|
|
||||||
command: ["bash", "-c", "[[ -d \"$HOME/.claude\" ]] || command -v claude >/dev/null"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
root.installed = exitCode === 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: projectScanner
|
id: projectScanner
|
||||||
running: false
|
running: false
|
||||||
@@ -146,6 +138,23 @@ Item {
|
|||||||
onTriggered: root.probeRateLimits(false)
|
onTriggered: root.probeRateLimits(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The first probe fires seconds after login, often before DHCP has handed
|
||||||
|
// out a route, and comes back as a transport failure rather than an answer
|
||||||
|
// from Anthropic. Try again shortly instead of showing "limits unavailable"
|
||||||
|
// until the next background poll a quarter of an hour later.
|
||||||
|
Timer {
|
||||||
|
id: probeRetry
|
||||||
|
interval: root.probeRetryMs
|
||||||
|
repeat: false
|
||||||
|
// Straight to the probe: a retry that answered to the same throttle
|
||||||
|
// that spaces out ordinary polls would never get off the ground.
|
||||||
|
onTriggered: if (root.enabled && root.oauthAccessToken && !root.oauthTokenExpired()) root.probeOAuthUsage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credentials load whether or not the panel wants this provider, so a
|
||||||
|
// switched-off Claude must not keep knocking on a downed network forever.
|
||||||
|
onEnabledChanged: if (!enabled) probeRetry.stop()
|
||||||
|
|
||||||
function localDateString() {
|
function localDateString() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const y = now.getFullYear();
|
const y = now.getFullYear();
|
||||||
@@ -278,7 +287,10 @@ Item {
|
|||||||
if (root.oauthAccessToken && !root.oauthTokenExpired()) {
|
if (root.oauthAccessToken && !root.oauthTokenExpired()) {
|
||||||
if (root.usageStatusText === "Waiting for auth")
|
if (root.usageStatusText === "Waiting for auth")
|
||||||
root.clearUsageStatus();
|
root.clearUsageStatus();
|
||||||
root.probeRateLimits(false);
|
// A fresh token just wiped the limits it replaced, so don't let
|
||||||
|
// the old token's throttle keep them blank for the next quarter
|
||||||
|
// of an hour.
|
||||||
|
root.probeRateLimits(tokenChanged);
|
||||||
} else if (!root.oauthAccessToken) {
|
} else if (!root.oauthAccessToken) {
|
||||||
root.usageStatusText = "Waiting for auth";
|
root.usageStatusText = "Waiting for auth";
|
||||||
root.clearAuthoritativeRateLimits();
|
root.clearAuthoritativeRateLimits();
|
||||||
@@ -425,7 +437,13 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function probeOAuthUsage() {
|
function probeOAuthUsage() {
|
||||||
|
// One probe at a time. The retry timer and a forced panel-open refresh
|
||||||
|
// can otherwise both be in flight, and the slower answer wins.
|
||||||
|
if (root.probeInFlight)
|
||||||
|
return;
|
||||||
|
root.probeInFlight = true;
|
||||||
root.refreshing = true;
|
root.refreshing = true;
|
||||||
|
root.lastProbeAtMs = Date.now();
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open("GET", "https://api.anthropic.com/api/oauth/usage");
|
xhr.open("GET", "https://api.anthropic.com/api/oauth/usage");
|
||||||
xhr.setRequestHeader("Authorization", "Bearer " + root.oauthAccessToken);
|
xhr.setRequestHeader("Authorization", "Bearer " + root.oauthAccessToken);
|
||||||
@@ -434,6 +452,7 @@ Item {
|
|||||||
xhr.onreadystatechange = function () {
|
xhr.onreadystatechange = function () {
|
||||||
if (xhr.readyState !== XMLHttpRequest.DONE)
|
if (xhr.readyState !== XMLHttpRequest.DONE)
|
||||||
return;
|
return;
|
||||||
|
root.probeInFlight = false;
|
||||||
|
|
||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
try {
|
try {
|
||||||
@@ -442,6 +461,7 @@ Item {
|
|||||||
const sessionBucket = root.oauthUsageBucket(payload, "five_hour");
|
const sessionBucket = root.oauthUsageBucket(payload, "five_hour");
|
||||||
|
|
||||||
if (root.applyAuthoritativeRateLimits(weeklyBucket?.utilization, weeklyBucket?.resets_at, sessionBucket?.utilization, sessionBucket?.resets_at, "")) {
|
if (root.applyAuthoritativeRateLimits(weeklyBucket?.utilization, weeklyBucket?.resets_at, sessionBucket?.utilization, sessionBucket?.resets_at, "")) {
|
||||||
|
probeRetry.stop();
|
||||||
root.clearUsageStatus();
|
root.clearUsageStatus();
|
||||||
root.finishRefresh();
|
root.finishRefresh();
|
||||||
return;
|
return;
|
||||||
@@ -456,10 +476,20 @@ Item {
|
|||||||
console.warn("model-usage/claude", "OAuth usage probe unavailable (status " + xhr.status + ")" + (body ? " body=" + body : ""));
|
console.warn("model-usage/claude", "OAuth usage probe unavailable (status " + xhr.status + ")" + (body ? " body=" + body : ""));
|
||||||
if (!root.hasAuthoritativeRateLimit) {
|
if (!root.hasAuthoritativeRateLimit) {
|
||||||
root.usageStatusText = "Claude limits unavailable";
|
root.usageStatusText = "Claude limits unavailable";
|
||||||
root.authHelpText = xhr.status === 429
|
root.authHelpText = xhr.status === 0
|
||||||
|
? "Couldn't reach Anthropic's usage endpoint. Retrying shortly. Local Claude Code stats are still shown."
|
||||||
|
: xhr.status === 429
|
||||||
? "Anthropic's usage endpoint is rate limiting checks right now" + (retryAfter ? " (retry after " + retryAfter + "s)" : "") + ". Local Claude Code stats are still shown."
|
? "Anthropic's usage endpoint is rate limiting checks right now" + (retryAfter ? " (retry after " + retryAfter + "s)" : "") + ". Local Claude Code stats are still shown."
|
||||||
: "Anthropic's usage endpoint returned status " + xhr.status + ". Local Claude Code stats are still shown.";
|
: "Anthropic's usage endpoint returned status " + xhr.status + ". Local Claude Code stats are still shown.";
|
||||||
}
|
}
|
||||||
|
// Status 0 is a transport failure — no route, no DNS, no server
|
||||||
|
// reached. Nothing to be a good citizen about, so keep knocking.
|
||||||
|
// Any real answer, including 429, disarms the retry: a server that
|
||||||
|
// replied is a server we should stop pestering.
|
||||||
|
if (xhr.status === 0)
|
||||||
|
probeRetry.restart();
|
||||||
|
else
|
||||||
|
probeRetry.stop();
|
||||||
root.finishRefresh();
|
root.finishRefresh();
|
||||||
};
|
};
|
||||||
xhr.send();
|
xhr.send();
|
||||||
@@ -482,8 +512,6 @@ Item {
|
|||||||
|
|
||||||
function refresh(force) {
|
function refresh(force) {
|
||||||
root.refreshing = true;
|
root.refreshing = true;
|
||||||
if (!presenceProbe.running)
|
|
||||||
presenceProbe.running = true;
|
|
||||||
statsFile.reload();
|
statsFile.reload();
|
||||||
historyFile.reload();
|
historyFile.reload();
|
||||||
credentialsFile.reload();
|
credentialsFile.reload();
|
||||||
@@ -493,6 +521,12 @@ Item {
|
|||||||
root.probeRateLimits(force === true);
|
root.probeRateLimits(force === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The cheap half of a refresh: Anthropic's numbers without the disk walk.
|
||||||
|
function refreshLimits() {
|
||||||
|
if (root.oauthAccessToken && root.authMode === "oauth" && !root.oauthTokenExpired())
|
||||||
|
root.probeRateLimits(true);
|
||||||
|
}
|
||||||
|
|
||||||
function formatResetTime(isoTimestamp) {
|
function formatResetTime(isoTimestamp) {
|
||||||
if (!isoTimestamp)
|
if (!isoTimestamp)
|
||||||
return "";
|
return "";
|
||||||
@@ -524,12 +558,11 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nowMs = Date.now();
|
const minIntervalMs = force === true ? root.probeForcedMinIntervalMs : root.probeMinIntervalMs;
|
||||||
if (force !== true && root.lastProbeAtMs > 0 && (nowMs - root.lastProbeAtMs) < root.probeMinIntervalMs) {
|
if (root.lastProbeAtMs > 0 && (Date.now() - root.lastProbeAtMs) < minIntervalMs) {
|
||||||
root.finishRefresh();
|
root.finishRefresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root.lastProbeAtMs = nowMs;
|
|
||||||
|
|
||||||
root.probeOAuthUsage();
|
root.probeOAuthUsage();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ Item {
|
|||||||
|
|
||||||
property string tierLabel: ""
|
property string tierLabel: ""
|
||||||
property string usageStatusText: ""
|
property string usageStatusText: ""
|
||||||
// Optimistic until the probe answers, so a present provider never blinks
|
|
||||||
// out of the panel on startup.
|
|
||||||
property bool installed: true
|
|
||||||
property string authHelpText: "Run `codex login` to authenticate."
|
property string authHelpText: "Run `codex login` to authenticate."
|
||||||
property bool hasLocalStats: true
|
property bool hasLocalStats: true
|
||||||
|
|
||||||
@@ -46,17 +43,6 @@ Item {
|
|||||||
|
|
||||||
readonly property string scannerPath: String(Qt.resolvedUrl("../scripts/codex_usage_scanner.py")).replace("file://", "")
|
readonly property string scannerPath: String(Qt.resolvedUrl("../scripts/codex_usage_scanner.py")).replace("file://", "")
|
||||||
|
|
||||||
// Codex is "here" if its state directory, its session store, or the CLI
|
|
||||||
// exists. Re-runs on refresh so installing it mid-session shows up.
|
|
||||||
Process {
|
|
||||||
id: presenceProbe
|
|
||||||
running: true
|
|
||||||
command: ["bash", "-c", "[[ -d \"$HOME/.codex\" ]] || [[ -d \"$HOME/.pi/agent/sessions\" ]] || command -v codex >/dev/null"]
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
root.installed = exitCode === 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: usageScanner
|
id: usageScanner
|
||||||
command: ["python3", root.scannerPath]
|
command: ["python3", root.scannerPath]
|
||||||
@@ -89,14 +75,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refresh(force) {
|
function refresh(force) {
|
||||||
if (!presenceProbe.running)
|
|
||||||
presenceProbe.running = true
|
|
||||||
if (usageScanner.running)
|
if (usageScanner.running)
|
||||||
return
|
return
|
||||||
root.refreshing = true
|
root.refreshing = true
|
||||||
usageScanner.running = true
|
usageScanner.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Codex reports limits and local stats from the same scanner run, and that
|
||||||
|
// run has no expensive mode to skip, so there is nothing cheaper to do.
|
||||||
|
function refreshLimits() { refresh() }
|
||||||
|
|
||||||
function parseScannerOutput(output) {
|
function parseScannerOutput(output) {
|
||||||
const raw = String(output || "").trim()
|
const raw = String(output || "").trim()
|
||||||
if (raw === "")
|
if (raw === "")
|
||||||
|
|||||||
Reference in New Issue
Block a user