agentshield-sdk 7.2.0 → 7.3.0

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.
@@ -1,359 +1,403 @@
1
- 'use strict';
2
-
3
- /**
4
- * Agent Shield — Distributed Scanning (v2.1)
5
- *
6
- * Share threat state, scan results, and pattern updates across multiple
7
- * Agent Shield instances. Includes an in-memory adapter for single-process
8
- * use and adapter interfaces for Redis, Memcached, etc.
9
- *
10
- * Zero dependencies — uses Node.js built-in modules only.
11
- */
12
-
13
- const crypto = require('crypto');
14
- const { EventEmitter } = require('events');
15
-
16
- // =========================================================================
17
- // ADAPTER INTERFACE
18
- // =========================================================================
19
-
20
- /**
21
- * Base adapter class. Extend this for custom backends (Redis, Memcached, etc.).
22
- */
23
- class DistributedAdapter extends EventEmitter {
24
- /**
25
- * Store a value.
26
- * @param {string} key
27
- * @param {*} value
28
- * @param {number} [ttlMs] - Time-to-live in milliseconds.
29
- * @returns {Promise<void>}
30
- */
31
- async set(key, value, ttlMs) { throw new Error('Not implemented'); }
32
-
33
- /**
34
- * Retrieve a value.
35
- * @param {string} key
36
- * @returns {Promise<*>}
37
- */
38
- async get(key) { throw new Error('Not implemented'); }
39
-
40
- /**
41
- * Delete a key.
42
- * @param {string} key
43
- * @returns {Promise<boolean>}
44
- */
45
- async del(key) { throw new Error('Not implemented'); }
46
-
47
- /**
48
- * Publish a message to a channel.
49
- * @param {string} channel
50
- * @param {*} message
51
- * @returns {Promise<void>}
52
- */
53
- async publish(channel, message) { throw new Error('Not implemented'); }
54
-
55
- /**
56
- * Subscribe to a channel.
57
- * @param {string} channel
58
- * @param {Function} handler
59
- * @returns {Promise<void>}
60
- */
61
- async subscribe(channel, handler) { throw new Error('Not implemented'); }
62
-
63
- /**
64
- * Increment a counter atomically.
65
- * @param {string} key
66
- * @param {number} [amount=1]
67
- * @returns {Promise<number>} New value.
68
- */
69
- async incr(key, amount = 1) { throw new Error('Not implemented'); }
70
- }
71
-
72
- // =========================================================================
73
- // IN-MEMORY ADAPTER
74
- // =========================================================================
75
-
76
- /**
77
- * In-memory adapter for single-process or testing use.
78
- */
79
- class MemoryAdapter extends DistributedAdapter {
80
- constructor() {
81
- super();
82
- this._store = new Map();
83
- this._subscriptions = new Map();
84
- this._timers = new Map();
85
- }
86
-
87
- async set(key, value, ttlMs) {
88
- this._store.set(key, value);
89
- if (ttlMs) {
90
- if (this._timers.has(key)) clearTimeout(this._timers.get(key));
91
- this._timers.set(key, setTimeout(() => {
92
- this._store.delete(key);
93
- this._timers.delete(key);
94
- }, ttlMs));
95
- }
96
- }
97
-
98
- async get(key) {
99
- return this._store.get(key) || null;
100
- }
101
-
102
- async del(key) {
103
- if (this._timers.has(key)) clearTimeout(this._timers.get(key));
104
- this._timers.delete(key);
105
- return this._store.delete(key);
106
- }
107
-
108
- async publish(channel, message) {
109
- const handlers = this._subscriptions.get(channel) || [];
110
- for (const handler of handlers) {
111
- handler(message);
112
- }
113
- }
114
-
115
- async subscribe(channel, handler) {
116
- if (!this._subscriptions.has(channel)) {
117
- this._subscriptions.set(channel, []);
118
- }
119
- this._subscriptions.get(channel).push(handler);
120
- }
121
-
122
- async incr(key, amount = 1) {
123
- const current = this._store.get(key) || 0;
124
- const newVal = current + amount;
125
- this._store.set(key, newVal);
126
- return newVal;
127
- }
128
-
129
- /**
130
- * Get all keys matching a prefix.
131
- * @param {string} prefix
132
- * @returns {Promise<string[]>}
133
- */
134
- async keys(prefix) {
135
- return [...this._store.keys()].filter(k => k.startsWith(prefix));
136
- }
137
-
138
- destroy() {
139
- for (const timer of this._timers.values()) clearTimeout(timer);
140
- this._timers.clear();
141
- this._store.clear();
142
- this._subscriptions.clear();
143
- }
144
- }
145
-
146
- // =========================================================================
147
- // REDIS ADAPTER TEMPLATE
148
- // =========================================================================
149
-
150
- /**
151
- * Redis adapter template. Users provide their own redis client instance.
152
- * Usage: new RedisAdapter({ client: require('redis').createClient() })
153
- */
154
- class RedisAdapter extends DistributedAdapter {
155
- /**
156
- * @param {object} options
157
- * @param {object} options.client - A Redis client instance (e.g., ioredis or node-redis).
158
- * @param {string} [options.prefix='agent-shield:'] - Key prefix.
159
- */
160
- constructor(options = {}) {
161
- super();
162
- this.client = options.client;
163
- this.prefix = options.prefix || 'agent-shield:';
164
-
165
- if (!this.client) {
166
- throw new Error('RedisAdapter requires a Redis client instance. Pass { client: redisClient }.');
167
- }
168
- }
169
-
170
- async set(key, value, ttlMs) {
171
- const serialized = JSON.stringify(value);
172
- if (ttlMs) {
173
- await this.client.set(this.prefix + key, serialized, 'PX', ttlMs);
174
- } else {
175
- await this.client.set(this.prefix + key, serialized);
176
- }
177
- }
178
-
179
- async get(key) {
180
- const result = await this.client.get(this.prefix + key);
181
- return result ? JSON.parse(result) : null;
182
- }
183
-
184
- async del(key) {
185
- const result = await this.client.del(this.prefix + key);
186
- return result > 0;
187
- }
188
-
189
- async publish(channel, message) {
190
- await this.client.publish(this.prefix + channel, JSON.stringify(message));
191
- }
192
-
193
- async subscribe(channel, handler) {
194
- const subscriber = this.client.duplicate();
195
- await subscriber.subscribe(this.prefix + channel);
196
- subscriber.on('message', (ch, msg) => {
197
- try {
198
- handler(JSON.parse(msg));
199
- } catch (e) {
200
- handler(msg);
201
- }
202
- });
203
- }
204
-
205
- async incr(key, amount = 1) {
206
- return this.client.incrby(this.prefix + key, amount);
207
- }
208
- }
209
-
210
- // =========================================================================
211
- // DISTRIBUTED SHIELD
212
- // =========================================================================
213
-
214
- /**
215
- * Coordinates multiple Agent Shield instances sharing threat state.
216
- */
217
- class DistributedShield {
218
- /**
219
- * @param {object} [options]
220
- * @param {DistributedAdapter} [options.adapter] - Storage adapter (defaults to MemoryAdapter).
221
- * @param {string} [options.instanceId] - Unique ID for this instance.
222
- * @param {number} [options.syncIntervalMs=30000] - How often to sync state.
223
- * @param {number} [options.threatTTLMs=3600000] - How long threats persist (1 hour default).
224
- */
225
- constructor(options = {}) {
226
- this.adapter = options.adapter || new MemoryAdapter();
227
- this.instanceId = options.instanceId || crypto.randomBytes(8).toString('hex');
228
- this.syncIntervalMs = options.syncIntervalMs || 30000;
229
- this.threatTTLMs = options.threatTTLMs || 3600000;
230
-
231
- this._localThreats = [];
232
- this._syncTimer = null;
233
- this._started = false;
234
-
235
- console.log('[Agent Shield] DistributedShield initialized (instance: %s)', this.instanceId);
236
- }
237
-
238
- /**
239
- * Start distributed coordination.
240
- * @returns {Promise<void>}
241
- */
242
- async start() {
243
- if (this._started) return;
244
- this._started = true;
245
-
246
- // Register this instance
247
- await this.adapter.set(`instance:${this.instanceId}`, {
248
- id: this.instanceId,
249
- startedAt: Date.now(),
250
- lastHeartbeat: Date.now()
251
- }, this.threatTTLMs);
252
-
253
- // Subscribe to threat broadcasts
254
- await this.adapter.subscribe('threats', (threat) => {
255
- if (threat.instanceId !== this.instanceId) {
256
- this._localThreats.push(threat);
257
- console.log('[Agent Shield] Received threat from instance %s: %s', threat.instanceId, threat.category);
258
- }
259
- });
260
-
261
- // Heartbeat
262
- this._syncTimer = setInterval(async () => {
263
- await this.adapter.set(`instance:${this.instanceId}`, {
264
- id: this.instanceId,
265
- startedAt: Date.now(),
266
- lastHeartbeat: Date.now()
267
- }, this.threatTTLMs);
268
- }, this.syncIntervalMs);
269
-
270
- console.log('[Agent Shield] DistributedShield started');
271
- }
272
-
273
- /**
274
- * Report a detected threat to all instances.
275
- * @param {object} threat - Threat object from a scan result.
276
- * @returns {Promise<void>}
277
- */
278
- async reportThreat(threat) {
279
- const entry = {
280
- ...threat,
281
- instanceId: this.instanceId,
282
- timestamp: Date.now(),
283
- id: crypto.randomBytes(8).toString('hex')
284
- };
285
-
286
- // Store in shared state
287
- await this.adapter.set(`threat:${entry.id}`, entry, this.threatTTLMs);
288
- await this.adapter.incr('stats:totalThreats');
289
- await this.adapter.incr(`stats:category:${threat.category || 'unknown'}`);
290
-
291
- // Broadcast to other instances
292
- await this.adapter.publish('threats', entry);
293
-
294
- this._localThreats.push(entry);
295
- }
296
-
297
- /**
298
- * Get aggregated threat statistics across all instances.
299
- * @returns {Promise<object>}
300
- */
301
- async getGlobalStats() {
302
- const totalThreats = await this.adapter.get('stats:totalThreats') || 0;
303
-
304
- return {
305
- instanceId: this.instanceId,
306
- totalThreats,
307
- localThreats: this._localThreats.length,
308
- started: this._started
309
- };
310
- }
311
-
312
- /**
313
- * Check if a threat signature has been seen by any instance.
314
- * @param {string} signature - Threat hash/signature.
315
- * @returns {Promise<boolean>}
316
- */
317
- async isKnownThreat(signature) {
318
- const result = await this.adapter.get(`threat:sig:${signature}`);
319
- return result !== null;
320
- }
321
-
322
- /**
323
- * Mark a threat signature as known.
324
- * @param {string} signature
325
- * @param {object} [metadata]
326
- * @returns {Promise<void>}
327
- */
328
- async markKnownThreat(signature, metadata = {}) {
329
- await this.adapter.set(`threat:sig:${signature}`, {
330
- ...metadata,
331
- firstSeen: Date.now(),
332
- reportedBy: this.instanceId
333
- }, this.threatTTLMs);
334
- }
335
-
336
- /**
337
- * Stop distributed coordination.
338
- */
339
- async stop() {
340
- if (this._syncTimer) {
341
- clearInterval(this._syncTimer);
342
- this._syncTimer = null;
343
- }
344
- this._started = false;
345
- await this.adapter.del(`instance:${this.instanceId}`);
346
- console.log('[Agent Shield] DistributedShield stopped');
347
- }
348
- }
349
-
350
- // =========================================================================
351
- // EXPORTS
352
- // =========================================================================
353
-
354
- module.exports = {
355
- DistributedShield,
356
- DistributedAdapter,
357
- MemoryAdapter,
358
- RedisAdapter
359
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Distributed Scanning (v2.1)
5
+ *
6
+ * Share threat state, scan results, and pattern updates across multiple
7
+ * Agent Shield instances. Includes an in-memory adapter for single-process
8
+ * use and adapter interfaces for Redis, Memcached, etc.
9
+ *
10
+ * Zero dependencies — uses Node.js built-in modules only.
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+ const { EventEmitter } = require('events');
15
+ const { createShieldError } = require('./errors');
16
+
17
+ // =========================================================================
18
+ // ADAPTER INTERFACE
19
+ // =========================================================================
20
+
21
+ /**
22
+ * Base adapter class. Extend this for custom backends (Redis, Memcached, etc.).
23
+ */
24
+ class DistributedAdapter extends EventEmitter {
25
+ /**
26
+ * Store a value.
27
+ * @param {string} key
28
+ * @param {*} value
29
+ * @param {number} [ttlMs] - Time-to-live in milliseconds.
30
+ * @returns {Promise<void>}
31
+ */
32
+ async set(key, value, ttlMs) { throw new Error('Not implemented'); }
33
+
34
+ /**
35
+ * Retrieve a value.
36
+ * @param {string} key
37
+ * @returns {Promise<*>}
38
+ */
39
+ async get(key) { throw new Error('Not implemented'); }
40
+
41
+ /**
42
+ * Delete a key.
43
+ * @param {string} key
44
+ * @returns {Promise<boolean>}
45
+ */
46
+ async del(key) { throw new Error('Not implemented'); }
47
+
48
+ /**
49
+ * Publish a message to a channel.
50
+ * @param {string} channel
51
+ * @param {*} message
52
+ * @returns {Promise<void>}
53
+ */
54
+ async publish(channel, message) { throw new Error('Not implemented'); }
55
+
56
+ /**
57
+ * Subscribe to a channel.
58
+ * @param {string} channel
59
+ * @param {Function} handler
60
+ * @returns {Promise<void>}
61
+ */
62
+ async subscribe(channel, handler) { throw new Error('Not implemented'); }
63
+
64
+ /**
65
+ * Increment a counter atomically.
66
+ * @param {string} key
67
+ * @param {number} [amount=1]
68
+ * @returns {Promise<number>} New value.
69
+ */
70
+ async incr(key, amount = 1) { throw new Error('Not implemented'); }
71
+ }
72
+
73
+ // =========================================================================
74
+ // IN-MEMORY ADAPTER
75
+ // =========================================================================
76
+
77
+ /**
78
+ * In-memory adapter for single-process or testing use.
79
+ */
80
+ class MemoryAdapter extends DistributedAdapter {
81
+ constructor() {
82
+ super();
83
+ this._store = new Map();
84
+ this._subscriptions = new Map();
85
+ this._timers = new Map();
86
+ }
87
+
88
+ async set(key, value, ttlMs) {
89
+ this._store.set(key, value);
90
+ if (ttlMs) {
91
+ if (this._timers.has(key)) clearTimeout(this._timers.get(key));
92
+ this._timers.set(key, setTimeout(() => {
93
+ this._store.delete(key);
94
+ this._timers.delete(key);
95
+ }, ttlMs));
96
+ }
97
+ }
98
+
99
+ async get(key) {
100
+ return this._store.get(key) || null;
101
+ }
102
+
103
+ async del(key) {
104
+ if (this._timers.has(key)) clearTimeout(this._timers.get(key));
105
+ this._timers.delete(key);
106
+ return this._store.delete(key);
107
+ }
108
+
109
+ async publish(channel, message) {
110
+ const handlers = this._subscriptions.get(channel) || [];
111
+ for (const handler of handlers) {
112
+ handler(message);
113
+ }
114
+ }
115
+
116
+ async subscribe(channel, handler) {
117
+ if (!this._subscriptions.has(channel)) {
118
+ this._subscriptions.set(channel, []);
119
+ }
120
+ this._subscriptions.get(channel).push(handler);
121
+ }
122
+
123
+ async incr(key, amount = 1) {
124
+ const current = this._store.get(key) || 0;
125
+ const newVal = current + amount;
126
+ this._store.set(key, newVal);
127
+ return newVal;
128
+ }
129
+
130
+ /**
131
+ * Get all keys matching a prefix.
132
+ * @param {string} prefix
133
+ * @returns {Promise<string[]>}
134
+ */
135
+ async keys(prefix) {
136
+ return [...this._store.keys()].filter(k => k.startsWith(prefix));
137
+ }
138
+
139
+ destroy() {
140
+ for (const timer of this._timers.values()) clearTimeout(timer);
141
+ this._timers.clear();
142
+ this._store.clear();
143
+ this._subscriptions.clear();
144
+ }
145
+ }
146
+
147
+ // =========================================================================
148
+ // REDIS ADAPTER TEMPLATE
149
+ // =========================================================================
150
+
151
+ /**
152
+ * Redis adapter template. Users provide their own redis client instance.
153
+ * Usage: new RedisAdapter({ client: require('redis').createClient() })
154
+ */
155
+ class RedisAdapter extends DistributedAdapter {
156
+ /**
157
+ * @param {object} options
158
+ * @param {object} options.client - A Redis client instance (e.g., ioredis or node-redis).
159
+ * @param {string} [options.prefix='agent-shield:'] - Key prefix.
160
+ */
161
+ constructor(options = {}) {
162
+ super();
163
+ this.client = options.client;
164
+ this.prefix = options.prefix || 'agent-shield:';
165
+
166
+ if (!this.client) {
167
+ throw createShieldError('AS-NET-003', { reason: 'RedisAdapter requires a Redis client instance. Pass { client: redisClient }.' });
168
+ }
169
+ }
170
+
171
+ async set(key, value, ttlMs) {
172
+ const serialized = JSON.stringify(value);
173
+ if (ttlMs) {
174
+ await this.client.set(this.prefix + key, serialized, 'PX', ttlMs);
175
+ } else {
176
+ await this.client.set(this.prefix + key, serialized);
177
+ }
178
+ }
179
+
180
+ async get(key) {
181
+ const result = await this.client.get(this.prefix + key);
182
+ return result ? JSON.parse(result) : null;
183
+ }
184
+
185
+ async del(key) {
186
+ const result = await this.client.del(this.prefix + key);
187
+ return result > 0;
188
+ }
189
+
190
+ async publish(channel, message) {
191
+ await this.client.publish(this.prefix + channel, JSON.stringify(message));
192
+ }
193
+
194
+ async subscribe(channel, handler) {
195
+ const subscriber = this.client.duplicate();
196
+ await subscriber.subscribe(this.prefix + channel);
197
+ subscriber.on('message', (ch, msg) => {
198
+ try {
199
+ handler(JSON.parse(msg));
200
+ } catch (e) {
201
+ handler(msg);
202
+ }
203
+ });
204
+ }
205
+
206
+ async incr(key, amount = 1) {
207
+ return this.client.incrby(this.prefix + key, amount);
208
+ }
209
+ }
210
+
211
+ // =========================================================================
212
+ // DISTRIBUTED SHIELD
213
+ // =========================================================================
214
+
215
+ /**
216
+ * Coordinates multiple Agent Shield instances sharing threat state.
217
+ */
218
+ class DistributedShield {
219
+ /**
220
+ * @param {object} [options]
221
+ * @param {DistributedAdapter} [options.adapter] - Storage adapter (defaults to MemoryAdapter).
222
+ * @param {string} [options.instanceId] - Unique ID for this instance.
223
+ * @param {number} [options.syncIntervalMs=30000] - How often to sync state.
224
+ * @param {number} [options.threatTTLMs=3600000] - How long threats persist (1 hour default).
225
+ */
226
+ constructor(options = {}) {
227
+ this.adapter = options.adapter || new MemoryAdapter();
228
+ this.instanceId = options.instanceId || crypto.randomBytes(8).toString('hex');
229
+ this.syncIntervalMs = options.syncIntervalMs || 30000;
230
+ this.threatTTLMs = options.threatTTLMs || 3600000;
231
+
232
+ this._localThreats = [];
233
+ this._maxLocalThreats = options.maxLocalThreats || 1000;
234
+ this._syncTimer = null;
235
+ this._started = false;
236
+
237
+ // Queue depth monitoring
238
+ this._pendingOps = 0;
239
+ this._peakQueueDepth = 0;
240
+ this._totalOpsQueued = 0;
241
+
242
+ console.log('[Agent Shield] DistributedShield initialized (instance: %s)', this.instanceId);
243
+ }
244
+
245
+ /**
246
+ * Start distributed coordination.
247
+ * @returns {Promise<void>}
248
+ */
249
+ async start() {
250
+ if (this._started) return;
251
+ this._started = true;
252
+
253
+ // Register this instance
254
+ await this.adapter.set(`instance:${this.instanceId}`, {
255
+ id: this.instanceId,
256
+ startedAt: Date.now(),
257
+ lastHeartbeat: Date.now()
258
+ }, this.threatTTLMs);
259
+
260
+ // Subscribe to threat broadcasts
261
+ await this.adapter.subscribe('threats', (threat) => {
262
+ if (threat.instanceId !== this.instanceId) {
263
+ this._localThreats.push(threat);
264
+ if (this._localThreats.length > this._maxLocalThreats) {
265
+ this._localThreats = this._localThreats.slice(-Math.floor(this._maxLocalThreats * 0.75));
266
+ }
267
+ console.log('[Agent Shield] Received threat from instance %s: %s', threat.instanceId, threat.category);
268
+ }
269
+ });
270
+
271
+ // Heartbeat
272
+ this._syncTimer = setInterval(async () => {
273
+ await this.adapter.set(`instance:${this.instanceId}`, {
274
+ id: this.instanceId,
275
+ startedAt: Date.now(),
276
+ lastHeartbeat: Date.now()
277
+ }, this.threatTTLMs);
278
+ }, this.syncIntervalMs);
279
+ if (this._syncTimer.unref) this._syncTimer.unref();
280
+
281
+ console.log('[Agent Shield] DistributedShield started');
282
+ }
283
+
284
+ /**
285
+ * Report a detected threat to all instances.
286
+ * @param {object} threat - Threat object from a scan result.
287
+ * @returns {Promise<void>}
288
+ */
289
+ async reportThreat(threat) {
290
+ this._trackOp(1);
291
+ try {
292
+ const entry = {
293
+ ...threat,
294
+ instanceId: this.instanceId,
295
+ timestamp: Date.now(),
296
+ id: crypto.randomBytes(8).toString('hex')
297
+ };
298
+
299
+ // Store in shared state
300
+ await this.adapter.set(`threat:${entry.id}`, entry, this.threatTTLMs);
301
+ await this.adapter.incr('stats:totalThreats');
302
+ await this.adapter.incr(`stats:category:${threat.category || 'unknown'}`);
303
+
304
+ // Broadcast to other instances
305
+ await this.adapter.publish('threats', entry);
306
+
307
+ this._localThreats.push(entry);
308
+ if (this._localThreats.length > this._maxLocalThreats) {
309
+ this._localThreats = this._localThreats.slice(-Math.floor(this._maxLocalThreats * 0.75));
310
+ }
311
+ } finally {
312
+ this._trackOp(-1);
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Get aggregated threat statistics across all instances.
318
+ * @returns {Promise<object>}
319
+ */
320
+ async getGlobalStats() {
321
+ const totalThreats = await this.adapter.get('stats:totalThreats') || 0;
322
+
323
+ return {
324
+ instanceId: this.instanceId,
325
+ totalThreats,
326
+ localThreats: this._localThreats.length,
327
+ started: this._started
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Check if a threat signature has been seen by any instance.
333
+ * @param {string} signature - Threat hash/signature.
334
+ * @returns {Promise<boolean>}
335
+ */
336
+ async isKnownThreat(signature) {
337
+ const result = await this.adapter.get(`threat:sig:${signature}`);
338
+ return result !== null;
339
+ }
340
+
341
+ /**
342
+ * Mark a threat signature as known.
343
+ * @param {string} signature
344
+ * @param {object} [metadata]
345
+ * @returns {Promise<void>}
346
+ */
347
+ async markKnownThreat(signature, metadata = {}) {
348
+ await this.adapter.set(`threat:sig:${signature}`, {
349
+ ...metadata,
350
+ firstSeen: Date.now(),
351
+ reportedBy: this.instanceId
352
+ }, this.threatTTLMs);
353
+ }
354
+
355
+ /**
356
+ * Returns queue depth metrics for monitoring.
357
+ * @returns {{ pending: number, peak: number, totalQueued: number }}
358
+ */
359
+ getQueueDepth() {
360
+ return {
361
+ pending: this._pendingOps,
362
+ peak: this._peakQueueDepth,
363
+ totalQueued: this._totalOpsQueued
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Track pending async operations for queue depth monitoring.
369
+ * @param {number} delta - +1 when starting, -1 when completing.
370
+ * @private
371
+ */
372
+ _trackOp(delta) {
373
+ this._pendingOps += delta;
374
+ if (delta > 0) this._totalOpsQueued++;
375
+ if (this._pendingOps > this._peakQueueDepth) {
376
+ this._peakQueueDepth = this._pendingOps;
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Stop distributed coordination.
382
+ */
383
+ async stop() {
384
+ if (this._syncTimer) {
385
+ clearInterval(this._syncTimer);
386
+ this._syncTimer = null;
387
+ }
388
+ this._started = false;
389
+ await this.adapter.del(`instance:${this.instanceId}`);
390
+ console.log('[Agent Shield] DistributedShield stopped');
391
+ }
392
+ }
393
+
394
+ // =========================================================================
395
+ // EXPORTS
396
+ // =========================================================================
397
+
398
+ module.exports = {
399
+ DistributedShield,
400
+ DistributedAdapter,
401
+ MemoryAdapter,
402
+ RedisAdapter
403
+ };