@yemi33/minions 0.1.2077 → 0.1.2078
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/dashboard/js/refresh.js +41 -2
- package/package.json +1 -1
package/dashboard/js/refresh.js
CHANGED
|
@@ -511,8 +511,17 @@ let _lastStatusOkAt = Date.now();
|
|
|
511
511
|
let _consecutiveStatusFails = 0;
|
|
512
512
|
let _unreachableSince = 0; // 0 = currently reachable
|
|
513
513
|
let _unreachableAgeTimer = null;
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
// 3 + 20s (was 2 + 12s) — the prior thresholds tripped a banner on a single
|
|
515
|
+
// safeFetch abort (timeout 15s in state.js) because the OR'd age side was
|
|
516
|
+
// already satisfied. With safeFetch=15s, the age budget needs >15s to avoid
|
|
517
|
+
// the trip-on-first-slow-response footgun. 3 consecutive fails ≈ 12s of real
|
|
518
|
+
// outage at the 4s poll cadence, which still surfaces the banner promptly
|
|
519
|
+
// during a real dashboard crash but tolerates one slow rebuild + retry.
|
|
520
|
+
const _UNREACHABLE_FAIL_THRESHOLD = 3;
|
|
521
|
+
const _UNREACHABLE_AGE_MS = 20000;
|
|
522
|
+
// Once the banner is up we throttle polls (exponential, capped at 30s) to
|
|
523
|
+
// avoid hammering a struggling dashboard / network. Reset on recovery.
|
|
524
|
+
let _nextPollAllowedAt = 0;
|
|
516
525
|
|
|
517
526
|
function _formatAge(ms) {
|
|
518
527
|
if (ms < 1000) return 'just now';
|
|
@@ -595,10 +604,26 @@ window._resetDashboardUnreachableForTest = function() {
|
|
|
595
604
|
_lastStatusOkAt = Date.now();
|
|
596
605
|
_consecutiveStatusFails = 0;
|
|
597
606
|
_unreachableSince = 0;
|
|
607
|
+
_nextPollAllowedAt = 0;
|
|
598
608
|
if (_unreachableAgeTimer) { clearInterval(_unreachableAgeTimer); _unreachableAgeTimer = null; }
|
|
599
609
|
delete window._dashboardUnreachable;
|
|
600
610
|
};
|
|
601
611
|
|
|
612
|
+
// Visibility wake-up: Chromium throttles setInterval on hidden tabs to ~1/min,
|
|
613
|
+
// so _lastStatusOkAt can drift far past the age threshold while the tab is
|
|
614
|
+
// backgrounded. When the user refocuses, state.js fires refresh() (line 206)
|
|
615
|
+
// — without this reset the age side of the OR is already satisfied and a
|
|
616
|
+
// single transient post-wake failure (DNS not yet up after suspend, mid-
|
|
617
|
+
// restart server) trips the banner before any real evidence of trouble.
|
|
618
|
+
// Reset BEFORE state.js calls refresh() — listener order is fine because
|
|
619
|
+
// both fire on the same event; this listener registered first runs first.
|
|
620
|
+
document.addEventListener('visibilitychange', function() {
|
|
621
|
+
if (document.visibilityState === 'visible' && !_unreachableSince) {
|
|
622
|
+
_lastStatusOkAt = Date.now();
|
|
623
|
+
_consecutiveStatusFails = 0;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
602
627
|
// ── Refresh diagnostics (W-mphejzx100081972) ─────────────────────────────
|
|
603
628
|
// Ring buffer capturing the last 50 /api/status poll cycles so a user
|
|
604
629
|
// reporting "the dashboard didn't auto-update when X changed" can paste
|
|
@@ -649,6 +674,11 @@ document.addEventListener('visibilitychange', function() {
|
|
|
649
674
|
|
|
650
675
|
async function refresh() {
|
|
651
676
|
if (_refreshInFlight) return;
|
|
677
|
+
// Backoff gate — only active while the unreachable banner is up. Skips
|
|
678
|
+
// setInterval ticks until _nextPollAllowedAt is reached so a downed
|
|
679
|
+
// dashboard isn't hammered at the steady 4s cadence (which produces
|
|
680
|
+
// console-spam and adds load to whatever's wedged).
|
|
681
|
+
if (_nextPollAllowedAt && Date.now() < _nextPollAllowedAt) return;
|
|
652
682
|
_refreshInFlight = true;
|
|
653
683
|
const _diagOn = _isRefreshDiagOn();
|
|
654
684
|
const _t0 = _diagOn ? Date.now() : 0;
|
|
@@ -730,6 +760,7 @@ async function refresh() {
|
|
|
730
760
|
// instead of just dismissing the banner.
|
|
731
761
|
_lastStatusOkAt = Date.now();
|
|
732
762
|
_consecutiveStatusFails = 0;
|
|
763
|
+
_nextPollAllowedAt = 0;
|
|
733
764
|
if (_unreachableSince) _markDashboardReachable();
|
|
734
765
|
const _renderStart = _diagOn ? Date.now() : 0;
|
|
735
766
|
let _diagChanges = null;
|
|
@@ -762,6 +793,14 @@ async function refresh() {
|
|
|
762
793
|
if (_consecutiveStatusFails >= _UNREACHABLE_FAIL_THRESHOLD || ageMs > _UNREACHABLE_AGE_MS) {
|
|
763
794
|
_markDashboardUnreachable(e);
|
|
764
795
|
}
|
|
796
|
+
// Backoff: once we've tripped the banner, throttle subsequent polls
|
|
797
|
+
// (4s → 8s → 16s → 30s cap). Reset to 0 in the success path below so
|
|
798
|
+
// recovery snaps back to the steady 4s cadence on the next tick.
|
|
799
|
+
if (_unreachableSince) {
|
|
800
|
+
const failsSinceTrip = Math.max(1, _consecutiveStatusFails - _UNREACHABLE_FAIL_THRESHOLD + 1);
|
|
801
|
+
const backoffMs = Math.min(30000, 4000 * Math.pow(2, failsSinceTrip - 1));
|
|
802
|
+
_nextPollAllowedAt = Date.now() + backoffMs;
|
|
803
|
+
}
|
|
765
804
|
}
|
|
766
805
|
finally {
|
|
767
806
|
_refreshInFlight = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2078",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|