@upx-us/shield 0.6.4 → 0.6.5
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/CHANGELOG.md +14 -0
- package/dist/index.js +14 -0
- package/dist/src/case-monitor.d.ts +10 -3
- package/dist/src/case-monitor.js +85 -5
- package/openclaw.plugin.json +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.6.5] — 2026-03-09
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Smart degradation detection in `shield status`** — `getMonitorHealth()` now implements a three-gate algorithm to eliminate false positives from transient connectivity blips:
|
|
11
|
+
1. **Count gate**: `consecutiveFailures ≥ 5` (unchanged from v0.6.4)
|
|
12
|
+
2. **Time gate**: first failure in the current streak must be `> 3 minutes` old — a 1-minute connectivity blip (3 failures at 1m intervals) can never trigger DEGRADED
|
|
13
|
+
3. **Sticky recovery**: requires `≥ 2 consecutive successes` to clear DEGRADED — prevents flapping where one success clears it and then it degrades again next cycle
|
|
14
|
+
- **`MonitorHealth` interface** exported from `src/case-monitor.ts` — new fields: `status: 'ok' | 'degraded' | 'unknown'`, `consecutiveSuccesses`, `degradedSinceMs`, `lastCheckMs`, `lastErrorMessage`
|
|
15
|
+
- `status: 'unknown'` before the monitor has completed its first check cycle
|
|
16
|
+
- **`shield.status` RPC** — `caseMonitor` section now includes smart health fields: `status`, `degradedSince` (ISO string), `consecutiveFailures`, `lastError`, `lastCheck` (ISO string), and a pre-formatted `display` string (e.g. `⚠️ Case Monitor: DEGRADED — 7 consecutive failures since 2026-03-09T12:00:00.000Z. Last error: ...` or `✅ Case Monitor: ok`)
|
|
17
|
+
- **7 new tests** covering all degradation scenarios: unknown initial state, transient blip (count gate), time gate, true DEGRADED positive, sticky recovery (1 success not enough), recovery (2 successes clear), and RPC handler shape
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
7
21
|
## [0.6.4] — 2026-03-09
|
|
8
22
|
|
|
9
23
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -879,6 +879,10 @@ exports.default = {
|
|
|
879
879
|
const creds = (0, config_1.loadCredentials)();
|
|
880
880
|
const activated = state.activated || hasValidCredentials(creds);
|
|
881
881
|
const caseStatus = (0, case_monitor_1.getCaseMonitorStatus)();
|
|
882
|
+
const monitorHealth = (0, case_monitor_1.getMonitorHealth)();
|
|
883
|
+
const caseMonitorDisplay = monitorHealth.status === 'degraded'
|
|
884
|
+
? `⚠️ Case Monitor: DEGRADED — ${monitorHealth.consecutiveFailures} consecutive failures since ${monitorHealth.degradedSinceMs ? new Date(monitorHealth.degradedSinceMs).toISOString() : 'unknown'}. Last error: ${monitorHealth.lastErrorMessage}`
|
|
885
|
+
: `✅ Case Monitor: ok`;
|
|
882
886
|
respond(true, {
|
|
883
887
|
activated,
|
|
884
888
|
running: state.running,
|
|
@@ -893,6 +897,16 @@ exports.default = {
|
|
|
893
897
|
intervalMs: caseStatus.intervalMs,
|
|
894
898
|
nextCheckInMs: caseStatus.nextCheckIn,
|
|
895
899
|
lastCheckAt: caseStatus.lastCheckAt,
|
|
900
|
+
status: monitorHealth.status,
|
|
901
|
+
degradedSince: monitorHealth.degradedSinceMs
|
|
902
|
+
? new Date(monitorHealth.degradedSinceMs).toISOString()
|
|
903
|
+
: null,
|
|
904
|
+
consecutiveFailures: monitorHealth.consecutiveFailures,
|
|
905
|
+
lastError: monitorHealth.lastErrorMessage,
|
|
906
|
+
lastCheck: monitorHealth.lastCheckMs
|
|
907
|
+
? new Date(monitorHealth.lastCheckMs).toISOString()
|
|
908
|
+
: null,
|
|
909
|
+
display: caseMonitorDisplay,
|
|
896
910
|
},
|
|
897
911
|
});
|
|
898
912
|
});
|
|
@@ -56,10 +56,15 @@ export interface CaseDetail extends CaseSummary {
|
|
|
56
56
|
}
|
|
57
57
|
export declare function initCaseMonitor(dataDir: string): void;
|
|
58
58
|
export declare function notifyCaseMonitorActivity(): void;
|
|
59
|
-
export
|
|
60
|
-
status: 'ok' | 'degraded';
|
|
59
|
+
export interface MonitorHealth {
|
|
60
|
+
status: 'ok' | 'degraded' | 'unknown';
|
|
61
61
|
consecutiveFailures: number;
|
|
62
|
-
|
|
62
|
+
consecutiveSuccesses: number;
|
|
63
|
+
degradedSinceMs: number | null;
|
|
64
|
+
lastCheckMs: number | null;
|
|
65
|
+
lastErrorMessage: string | null;
|
|
66
|
+
}
|
|
67
|
+
export declare function getMonitorHealth(): MonitorHealth;
|
|
63
68
|
export declare function getCaseMonitorStatus(): {
|
|
64
69
|
intervalMs: number;
|
|
65
70
|
nextCheckIn: number;
|
|
@@ -70,4 +75,6 @@ export declare function getPendingCases(): CaseSummary[];
|
|
|
70
75
|
export declare function acknowledgeCases(caseIds: string[]): void;
|
|
71
76
|
export declare function formatCaseNotification(c: CaseSummary | CaseDetail): string;
|
|
72
77
|
export declare function _resetForTesting(): void;
|
|
78
|
+
export declare function _simulateFailuresForTesting(count: number, firstFailureAgeMs: number): void;
|
|
79
|
+
export declare function _simulateSuccessForTesting(): void;
|
|
73
80
|
export {};
|
package/dist/src/case-monitor.js
CHANGED
|
@@ -44,6 +44,8 @@ exports.getPendingCases = getPendingCases;
|
|
|
44
44
|
exports.acknowledgeCases = acknowledgeCases;
|
|
45
45
|
exports.formatCaseNotification = formatCaseNotification;
|
|
46
46
|
exports._resetForTesting = _resetForTesting;
|
|
47
|
+
exports._simulateFailuresForTesting = _simulateFailuresForTesting;
|
|
48
|
+
exports._simulateSuccessForTesting = _simulateSuccessForTesting;
|
|
47
49
|
const safe_io_1 = require("./safe-io");
|
|
48
50
|
const client_1 = require("./rpc/client");
|
|
49
51
|
const exclusions_1 = require("./exclusions");
|
|
@@ -164,7 +166,14 @@ let _lastEventAt = 0;
|
|
|
164
166
|
let _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
165
167
|
let _exclusionsWarnedOnce = false;
|
|
166
168
|
let _consecutiveCheckFailures = 0;
|
|
169
|
+
let _consecutiveSuccesses = 0;
|
|
170
|
+
let _degradedSinceMs = null;
|
|
171
|
+
let _firstFailureInStreakMs = null;
|
|
172
|
+
let _lastCheckMs = null;
|
|
173
|
+
let _lastErrorMessage = null;
|
|
167
174
|
const DEGRADED_THRESHOLD = 5;
|
|
175
|
+
const DEGRADED_TIME_GATE_MS = 3 * 60 * 1000;
|
|
176
|
+
const RECOVERY_THRESHOLD = 2;
|
|
168
177
|
function computeInterval() {
|
|
169
178
|
if (_lastCheckAt === 0)
|
|
170
179
|
return MIN_CHECK_INTERVAL_MS;
|
|
@@ -186,11 +195,58 @@ function notifyCaseMonitorActivity() {
|
|
|
186
195
|
_lastEventAt = Date.now();
|
|
187
196
|
}
|
|
188
197
|
function getMonitorHealth() {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const isDegraded = _consecutiveCheckFailures >= DEGRADED_THRESHOLD &&
|
|
200
|
+
_firstFailureInStreakMs !== null &&
|
|
201
|
+
(now - _firstFailureInStreakMs) >= DEGRADED_TIME_GATE_MS;
|
|
202
|
+
if (isDegraded && _degradedSinceMs === null) {
|
|
203
|
+
_degradedSinceMs = now;
|
|
204
|
+
}
|
|
205
|
+
let status;
|
|
206
|
+
if (_lastCheckMs === null) {
|
|
207
|
+
status = 'unknown';
|
|
208
|
+
}
|
|
209
|
+
else if (isDegraded) {
|
|
210
|
+
status = 'degraded';
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
status = 'ok';
|
|
214
|
+
}
|
|
189
215
|
return {
|
|
190
|
-
status
|
|
216
|
+
status,
|
|
191
217
|
consecutiveFailures: _consecutiveCheckFailures,
|
|
218
|
+
consecutiveSuccesses: _consecutiveSuccesses,
|
|
219
|
+
degradedSinceMs: _degradedSinceMs,
|
|
220
|
+
lastCheckMs: _lastCheckMs,
|
|
221
|
+
lastErrorMessage: _lastErrorMessage,
|
|
192
222
|
};
|
|
193
223
|
}
|
|
224
|
+
function recordCheckFailure(message) {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
if (_consecutiveCheckFailures === 0) {
|
|
227
|
+
_firstFailureInStreakMs = now;
|
|
228
|
+
}
|
|
229
|
+
_consecutiveCheckFailures++;
|
|
230
|
+
_consecutiveSuccesses = 0;
|
|
231
|
+
_lastCheckMs = now;
|
|
232
|
+
_lastErrorMessage = message;
|
|
233
|
+
if (_degradedSinceMs === null &&
|
|
234
|
+
_consecutiveCheckFailures >= DEGRADED_THRESHOLD &&
|
|
235
|
+
_firstFailureInStreakMs !== null &&
|
|
236
|
+
(now - _firstFailureInStreakMs) >= DEGRADED_TIME_GATE_MS) {
|
|
237
|
+
_degradedSinceMs = now;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function recordCheckSuccess() {
|
|
241
|
+
_consecutiveSuccesses++;
|
|
242
|
+
_lastCheckMs = Date.now();
|
|
243
|
+
_lastErrorMessage = null;
|
|
244
|
+
if (_consecutiveSuccesses >= RECOVERY_THRESHOLD) {
|
|
245
|
+
_consecutiveCheckFailures = 0;
|
|
246
|
+
_firstFailureInStreakMs = null;
|
|
247
|
+
_degradedSinceMs = null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
194
250
|
function getCaseMonitorStatus() {
|
|
195
251
|
const interval = computeInterval();
|
|
196
252
|
const nextCheckIn = Math.max(0, (_lastCheckAt + interval) - Date.now());
|
|
@@ -220,7 +276,7 @@ async function checkForNewCases(config) {
|
|
|
220
276
|
const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', params, 'GET');
|
|
221
277
|
if (!result.ok) {
|
|
222
278
|
if (!result.error?.includes('not configured')) {
|
|
223
|
-
|
|
279
|
+
recordCheckFailure(result.error ?? 'unknown error');
|
|
224
280
|
log.warn('case-monitor', `Failed to check cases: ${result.error}`);
|
|
225
281
|
if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
|
|
226
282
|
log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
|
|
@@ -260,11 +316,12 @@ async function checkForNewCases(config) {
|
|
|
260
316
|
}
|
|
261
317
|
state.lastCheckAt = new Date().toISOString();
|
|
262
318
|
(0, safe_io_1.writeJsonSafe)(stateFile, state);
|
|
263
|
-
|
|
319
|
+
recordCheckSuccess();
|
|
264
320
|
}
|
|
265
321
|
catch (err) {
|
|
266
|
-
|
|
267
|
-
|
|
322
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
323
|
+
recordCheckFailure(msg);
|
|
324
|
+
log.warn('case-monitor', `Check failed: ${msg}`);
|
|
268
325
|
if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
|
|
269
326
|
log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
|
|
270
327
|
}
|
|
@@ -369,6 +426,11 @@ function _resetForTesting() {
|
|
|
369
426
|
_currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
370
427
|
_exclusionsWarnedOnce = false;
|
|
371
428
|
_consecutiveCheckFailures = 0;
|
|
429
|
+
_consecutiveSuccesses = 0;
|
|
430
|
+
_degradedSinceMs = null;
|
|
431
|
+
_firstFailureInStreakMs = null;
|
|
432
|
+
_lastCheckMs = null;
|
|
433
|
+
_lastErrorMessage = null;
|
|
372
434
|
_enqueueSystemEvent = null;
|
|
373
435
|
_requestHeartbeatNow = null;
|
|
374
436
|
_agentId = 'main';
|
|
@@ -376,3 +438,21 @@ function _resetForTesting() {
|
|
|
376
438
|
_directSendTo = null;
|
|
377
439
|
_directSendChannel = null;
|
|
378
440
|
}
|
|
441
|
+
function _simulateFailuresForTesting(count, firstFailureAgeMs) {
|
|
442
|
+
const now = Date.now();
|
|
443
|
+
_firstFailureInStreakMs = now - firstFailureAgeMs;
|
|
444
|
+
_consecutiveCheckFailures = count;
|
|
445
|
+
_consecutiveSuccesses = 0;
|
|
446
|
+
_lastCheckMs = now - firstFailureAgeMs;
|
|
447
|
+
_lastErrorMessage = 'simulated failure';
|
|
448
|
+
}
|
|
449
|
+
function _simulateSuccessForTesting() {
|
|
450
|
+
_consecutiveSuccesses++;
|
|
451
|
+
_lastCheckMs = Date.now();
|
|
452
|
+
_lastErrorMessage = null;
|
|
453
|
+
if (_consecutiveSuccesses >= RECOVERY_THRESHOLD) {
|
|
454
|
+
_consecutiveCheckFailures = 0;
|
|
455
|
+
_firstFailureInStreakMs = null;
|
|
456
|
+
_degradedSinceMs = null;
|
|
457
|
+
}
|
|
458
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "shield",
|
|
3
3
|
"name": "OpenClaw Shield",
|
|
4
4
|
"description": "Real-time security monitoring \u2014 streams enriched, redacted security events to the Shield detection platform.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.5",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"clawhub": {
|
|
80
80
|
"slug": "openclaw-shield-upx",
|
|
81
|
-
"skillVersion": "
|
|
81
|
+
"skillVersion": "0.6.5",
|
|
82
82
|
"note": "ClawHub auto-increments on publish. Update this after each clawhub submission."
|
|
83
83
|
}
|
|
84
|
-
}
|
|
84
|
+
}
|
package/package.json
CHANGED