feat: Claude auto-ping to warm 5h window after reset

Auto-sends a minimal request right after each Claude OAuth connection's 5h quota window resets, so a fresh window starts immediately without waiting. Per-connection toggle on providers and quota dashboards.

- claudeAutoPing scheduler (server-side, 60s tick) hooked into initializeApp
- per-connection enable map in settings.claudeAutoPing.connections
- toggle + tooltip in ConnectionRow and ProviderLimits (Claude OAuth only)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
decolua
2026-06-17 09:31:46 +07:00
co-authored by Cursor
parent d03f9fb823
commit 740093d852
6 changed files with 204 additions and 2 deletions
@@ -50,6 +50,7 @@ export default function ProviderLimits() {
const [loading, setLoading] = useState({});
const [errors, setErrors] = useState({});
const [autoRefresh, setAutoRefresh] = useState(true);
const [autoPingMap, setAutoPingMap] = useState({});
const [lastUpdated, setLastUpdated] = useState(null);
const [hasHydratedAutoRefresh, setHasHydratedAutoRefresh] = useState(false);
const [refreshingAll, setRefreshingAll] = useState(false);
@@ -423,6 +424,31 @@ export default function ProviderLimits() {
window.localStorage.setItem(AUTO_REFRESH_STORAGE_KEY, String(autoRefresh));
}, [autoRefresh, hasHydratedAutoRefresh]);
// Load Claude auto-ping per-connection map
useEffect(() => {
fetch("/api/settings", { cache: "no-store" })
.then((r) => (r.ok ? r.json() : {}))
.then((s) => setAutoPingMap(s?.claudeAutoPing?.connections || {}))
.catch(() => {});
}, []);
const toggleAutoPing = useCallback(async (connectionId, on) => {
const next = { ...autoPingMap, [connectionId]: on };
setAutoPingMap(next);
try {
const r = await fetch("/api/settings", { cache: "no-store" });
const s = r.ok ? await r.json() : {};
const cfg = { ...(s.claudeAutoPing || {}), connections: next };
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ claudeAutoPing: cfg }),
});
} catch {
setAutoPingMap(autoPingMap);
}
}, [autoPingMap]);
// Auto-refresh interval
useEffect(() => {
if (!hasHydratedAutoRefresh || !autoRefresh) {
@@ -793,6 +819,7 @@ export default function ProviderLimits() {
)}
</button>
{/* Refresh all button */}
<button
type="button"
@@ -898,6 +925,18 @@ export default function ProviderLimits() {
</button>
</Tooltip>
)}
{conn.provider === "claude" && conn.authType === "oauth" && (
<Tooltip text="When your 5h quota runs out, auto-sends a request the moment it resets so a new window starts right away.">
<button
type="button"
onClick={() => toggleAutoPing(conn.id, !(autoPingMap[conn.id] === true))}
aria-label="Toggle auto-ping"
className={`flex h-8 w-8 items-center justify-center rounded-lg transition-colors hover:bg-black/5 dark:hover:bg-white/5 ${autoPingMap[conn.id] === true ? "text-primary" : "text-text-muted"}`}
>
<span className="material-symbols-outlined text-[18px]">bolt</span>
</button>
</Tooltip>
)}
<Tooltip text="Refresh quota">
<button
type="button"