claude-flow 1.0.21 → 1.0.23

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.
@@ -0,0 +1,473 @@
1
+ import { EventEmitter } from 'events';
2
+ import * as os from 'os';
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'path';
5
+ import { Logger } from '../core/logger';
6
+ import { performance } from 'perf_hooks';
7
+
8
+ interface AgentMetrics {
9
+ id: string;
10
+ name: string;
11
+ status: 'idle' | 'running' | 'completed' | 'failed' | 'stalled';
12
+ currentTask?: string;
13
+ startTime?: number;
14
+ endTime?: number;
15
+ duration?: number;
16
+ cpuUsage?: number;
17
+ memoryUsage?: number;
18
+ taskCount: number;
19
+ successCount: number;
20
+ failureCount: number;
21
+ averageTaskDuration: number;
22
+ lastActivity: number;
23
+ outputSize?: number;
24
+ errorRate: number;
25
+ }
26
+
27
+ interface SystemMetrics {
28
+ timestamp: number;
29
+ cpuUsage: number;
30
+ memoryUsage: number;
31
+ totalMemory: number;
32
+ freeMemory: number;
33
+ loadAverage: number[];
34
+ activeAgents: number;
35
+ totalTasks: number;
36
+ completedTasks: number;
37
+ failedTasks: number;
38
+ pendingTasks: number;
39
+ averageTaskDuration: number;
40
+ throughput: number; // tasks per minute
41
+ }
42
+
43
+ interface Alert {
44
+ id: string;
45
+ timestamp: number;
46
+ level: 'info' | 'warning' | 'error' | 'critical';
47
+ type: 'agent_failure' | 'high_cpu' | 'high_memory' | 'stalled_agent' | 'low_throughput' | 'error_rate';
48
+ message: string;
49
+ details?: any;
50
+ }
51
+
52
+ interface MonitoringConfig {
53
+ updateInterval: number; // milliseconds
54
+ metricsRetention: number; // hours
55
+ cpuThreshold: number; // percentage
56
+ memoryThreshold: number; // percentage
57
+ stallTimeout: number; // milliseconds
58
+ errorRateThreshold: number; // percentage
59
+ throughputThreshold: number; // tasks per minute
60
+ enableAlerts: boolean;
61
+ enableHistory: boolean;
62
+ historyPath?: string;
63
+ }
64
+
65
+ export class SwarmMonitor extends EventEmitter {
66
+ private logger: Logger;
67
+ private config: MonitoringConfig;
68
+ private agentMetrics: Map<string, AgentMetrics> = new Map();
69
+ private systemMetrics: SystemMetrics[] = [];
70
+ private alerts: Alert[] = [];
71
+ private monitoringInterval?: NodeJS.Timeout;
72
+ private startTime: number;
73
+ private taskStartTimes: Map<string, number> = new Map();
74
+ private taskCompletionTimes: number[] = [];
75
+ private lastThroughputCheck: number;
76
+ private tasksInLastMinute: number = 0;
77
+
78
+ constructor(config?: Partial<MonitoringConfig>) {
79
+ super();
80
+ this.logger = new Logger('SwarmMonitor');
81
+ this.config = {
82
+ updateInterval: 1000, // 1 second
83
+ metricsRetention: 24, // 24 hours
84
+ cpuThreshold: 80, // 80%
85
+ memoryThreshold: 85, // 85%
86
+ stallTimeout: 300000, // 5 minutes
87
+ errorRateThreshold: 10, // 10%
88
+ throughputThreshold: 1, // 1 task per minute minimum
89
+ enableAlerts: true,
90
+ enableHistory: true,
91
+ historyPath: './monitoring/history',
92
+ ...config
93
+ };
94
+ this.startTime = Date.now();
95
+ this.lastThroughputCheck = Date.now();
96
+ }
97
+
98
+ async start(): Promise<void> {
99
+ this.logger.info('Starting swarm monitoring...');
100
+
101
+ // Create history directory if needed
102
+ if (this.config.enableHistory && this.config.historyPath) {
103
+ await fs.mkdir(this.config.historyPath, { recursive: true });
104
+ }
105
+
106
+ // Start periodic monitoring
107
+ this.monitoringInterval = setInterval(() => {
108
+ this.collectMetrics();
109
+ }, this.config.updateInterval);
110
+
111
+ // Start initial collection
112
+ await this.collectMetrics();
113
+ }
114
+
115
+ stop(): void {
116
+ this.logger.info('Stopping swarm monitoring...');
117
+ if (this.monitoringInterval) {
118
+ clearInterval(this.monitoringInterval);
119
+ this.monitoringInterval = undefined;
120
+ }
121
+ }
122
+
123
+ // Agent registration and tracking
124
+ registerAgent(agentId: string, name: string): void {
125
+ this.agentMetrics.set(agentId, {
126
+ id: agentId,
127
+ name,
128
+ status: 'idle',
129
+ taskCount: 0,
130
+ successCount: 0,
131
+ failureCount: 0,
132
+ averageTaskDuration: 0,
133
+ lastActivity: Date.now(),
134
+ errorRate: 0
135
+ });
136
+ this.logger.debug(`Registered agent: ${name} (${agentId})`);
137
+ }
138
+
139
+ unregisterAgent(agentId: string): void {
140
+ const metrics = this.agentMetrics.get(agentId);
141
+ if (metrics) {
142
+ this.logger.debug(`Unregistered agent: ${metrics.name} (${agentId})`);
143
+ this.agentMetrics.delete(agentId);
144
+ }
145
+ }
146
+
147
+ // Task tracking
148
+ taskStarted(agentId: string, taskId: string, taskDescription?: string): void {
149
+ const metrics = this.agentMetrics.get(agentId);
150
+ if (metrics) {
151
+ metrics.status = 'running';
152
+ metrics.currentTask = taskDescription || taskId;
153
+ metrics.startTime = Date.now();
154
+ metrics.lastActivity = Date.now();
155
+ metrics.taskCount++;
156
+ this.taskStartTimes.set(taskId, Date.now());
157
+ this.emit('task:started', { agentId, taskId, taskDescription });
158
+ }
159
+ }
160
+
161
+ taskCompleted(agentId: string, taskId: string, outputSize?: number): void {
162
+ const metrics = this.agentMetrics.get(agentId);
163
+ const startTime = this.taskStartTimes.get(taskId);
164
+
165
+ if (metrics && startTime) {
166
+ const duration = Date.now() - startTime;
167
+ metrics.status = 'completed';
168
+ metrics.endTime = Date.now();
169
+ metrics.duration = duration;
170
+ metrics.lastActivity = Date.now();
171
+ metrics.successCount++;
172
+ metrics.outputSize = outputSize;
173
+
174
+ // Update average duration
175
+ const totalDuration = metrics.averageTaskDuration * (metrics.successCount - 1) + duration;
176
+ metrics.averageTaskDuration = totalDuration / metrics.successCount;
177
+
178
+ // Update error rate
179
+ metrics.errorRate = (metrics.failureCount / metrics.taskCount) * 100;
180
+
181
+ // Track for throughput calculation
182
+ this.taskCompletionTimes.push(Date.now());
183
+ this.tasksInLastMinute++;
184
+
185
+ this.taskStartTimes.delete(taskId);
186
+ this.emit('task:completed', { agentId, taskId, duration, outputSize });
187
+ }
188
+ }
189
+
190
+ taskFailed(agentId: string, taskId: string, error: string): void {
191
+ const metrics = this.agentMetrics.get(agentId);
192
+ const startTime = this.taskStartTimes.get(taskId);
193
+
194
+ if (metrics) {
195
+ const duration = startTime ? Date.now() - startTime : 0;
196
+ metrics.status = 'failed';
197
+ metrics.endTime = Date.now();
198
+ metrics.duration = duration;
199
+ metrics.lastActivity = Date.now();
200
+ metrics.failureCount++;
201
+
202
+ // Update error rate
203
+ metrics.errorRate = (metrics.failureCount / metrics.taskCount) * 100;
204
+
205
+ this.taskStartTimes.delete(taskId);
206
+ this.emit('task:failed', { agentId, taskId, error, duration });
207
+
208
+ // Check error rate threshold
209
+ if (metrics.errorRate > this.config.errorRateThreshold) {
210
+ this.createAlert('error_rate', 'critical',
211
+ `Agent ${metrics.name} has high error rate: ${metrics.errorRate.toFixed(1)}%`);
212
+ }
213
+ }
214
+ }
215
+
216
+ // Metrics collection
217
+ private async collectMetrics(): Promise<void> {
218
+ try {
219
+ // Collect system metrics
220
+ const cpuUsage = this.getCPUUsage();
221
+ const memInfo = this.getMemoryInfo();
222
+ const loadAvg = os.loadavg();
223
+
224
+ // Calculate throughput
225
+ const now = Date.now();
226
+ const minuteAgo = now - 60000;
227
+ this.taskCompletionTimes = this.taskCompletionTimes.filter(time => time > minuteAgo);
228
+ const throughput = this.taskCompletionTimes.length;
229
+
230
+ // Calculate task statistics
231
+ let totalTasks = 0;
232
+ let completedTasks = 0;
233
+ let failedTasks = 0;
234
+ let activeAgents = 0;
235
+ let totalDuration = 0;
236
+ let durationCount = 0;
237
+
238
+ // Check for stalled agents
239
+ for (const [agentId, metrics] of this.agentMetrics) {
240
+ if (metrics.status === 'running') {
241
+ activeAgents++;
242
+
243
+ // Check for stalled agent
244
+ const stallTime = now - metrics.lastActivity;
245
+ if (stallTime > this.config.stallTimeout) {
246
+ metrics.status = 'stalled';
247
+ this.createAlert('stalled_agent', 'warning',
248
+ `Agent ${metrics.name} appears to be stalled (${Math.round(stallTime / 1000)}s inactive)`);
249
+ }
250
+ }
251
+
252
+ totalTasks += metrics.taskCount;
253
+ completedTasks += metrics.successCount;
254
+ failedTasks += metrics.failureCount;
255
+
256
+ if (metrics.averageTaskDuration > 0) {
257
+ totalDuration += metrics.averageTaskDuration * metrics.successCount;
258
+ durationCount += metrics.successCount;
259
+ }
260
+ }
261
+
262
+ const avgDuration = durationCount > 0 ? totalDuration / durationCount : 0;
263
+ const pendingTasks = totalTasks - completedTasks - failedTasks;
264
+
265
+ // Create system metrics
266
+ const systemMetrics: SystemMetrics = {
267
+ timestamp: now,
268
+ cpuUsage,
269
+ memoryUsage: memInfo.usagePercent,
270
+ totalMemory: memInfo.total,
271
+ freeMemory: memInfo.free,
272
+ loadAverage: loadAvg,
273
+ activeAgents,
274
+ totalTasks,
275
+ completedTasks,
276
+ failedTasks,
277
+ pendingTasks,
278
+ averageTaskDuration: avgDuration,
279
+ throughput
280
+ };
281
+
282
+ this.systemMetrics.push(systemMetrics);
283
+
284
+ // Check system thresholds
285
+ if (this.config.enableAlerts) {
286
+ this.checkThresholds(systemMetrics);
287
+ }
288
+
289
+ // Clean old metrics
290
+ this.cleanOldMetrics();
291
+
292
+ // Save history if enabled
293
+ if (this.config.enableHistory) {
294
+ await this.saveHistory(systemMetrics);
295
+ }
296
+
297
+ // Emit metrics update
298
+ this.emit('metrics:updated', {
299
+ system: systemMetrics,
300
+ agents: Array.from(this.agentMetrics.values())
301
+ });
302
+
303
+ } catch (error) {
304
+ this.logger.error('Error collecting metrics:', error);
305
+ }
306
+ }
307
+
308
+ private getCPUUsage(): number {
309
+ const cpus = os.cpus();
310
+ let totalIdle = 0;
311
+ let totalTick = 0;
312
+
313
+ cpus.forEach(cpu => {
314
+ for (const type in cpu.times) {
315
+ totalTick += cpu.times[type as keyof typeof cpu.times];
316
+ }
317
+ totalIdle += cpu.times.idle;
318
+ });
319
+
320
+ return 100 - Math.floor(totalIdle / totalTick * 100);
321
+ }
322
+
323
+ private getMemoryInfo(): { total: number; free: number; used: number; usagePercent: number } {
324
+ const total = os.totalmem();
325
+ const free = os.freemem();
326
+ const used = total - free;
327
+ const usagePercent = (used / total) * 100;
328
+
329
+ return { total, free, used, usagePercent };
330
+ }
331
+
332
+ private checkThresholds(metrics: SystemMetrics): void {
333
+ // CPU threshold
334
+ if (metrics.cpuUsage > this.config.cpuThreshold) {
335
+ this.createAlert('high_cpu', 'warning',
336
+ `High CPU usage detected: ${metrics.cpuUsage}%`);
337
+ }
338
+
339
+ // Memory threshold
340
+ if (metrics.memoryUsage > this.config.memoryThreshold) {
341
+ this.createAlert('high_memory', 'warning',
342
+ `High memory usage detected: ${metrics.memoryUsage.toFixed(1)}%`);
343
+ }
344
+
345
+ // Throughput threshold
346
+ if (metrics.activeAgents > 0 && metrics.throughput < this.config.throughputThreshold) {
347
+ this.createAlert('low_throughput', 'warning',
348
+ `Low throughput detected: ${metrics.throughput} tasks/min`);
349
+ }
350
+ }
351
+
352
+ private createAlert(type: Alert['type'], level: Alert['level'], message: string, details?: any): void {
353
+ const alert: Alert = {
354
+ id: `${type}_${Date.now()}`,
355
+ timestamp: Date.now(),
356
+ level,
357
+ type,
358
+ message,
359
+ details
360
+ };
361
+
362
+ this.alerts.push(alert);
363
+ this.emit('alert', alert);
364
+ this.logger[level](message);
365
+ }
366
+
367
+ private cleanOldMetrics(): void {
368
+ const retentionTime = this.config.metricsRetention * 60 * 60 * 1000;
369
+ const cutoff = Date.now() - retentionTime;
370
+
371
+ this.systemMetrics = this.systemMetrics.filter(m => m.timestamp > cutoff);
372
+ this.alerts = this.alerts.filter(a => a.timestamp > cutoff);
373
+ }
374
+
375
+ private async saveHistory(metrics: SystemMetrics): Promise<void> {
376
+ if (!this.config.historyPath) return;
377
+
378
+ try {
379
+ const date = new Date();
380
+ const filename = `metrics_${date.toISOString().split('T')[0]}.jsonl`;
381
+ const filepath = path.join(this.config.historyPath, filename);
382
+
383
+ const line = JSON.stringify({
384
+ ...metrics,
385
+ agents: Array.from(this.agentMetrics.values())
386
+ }) + '\n';
387
+
388
+ await fs.appendFile(filepath, line);
389
+ } catch (error) {
390
+ this.logger.error('Error saving history:', error);
391
+ }
392
+ }
393
+
394
+ // Getters for current state
395
+ getSystemMetrics(): SystemMetrics | undefined {
396
+ return this.systemMetrics[this.systemMetrics.length - 1];
397
+ }
398
+
399
+ getAgentMetrics(agentId?: string): AgentMetrics | AgentMetrics[] | undefined {
400
+ if (agentId) {
401
+ return this.agentMetrics.get(agentId);
402
+ }
403
+ return Array.from(this.agentMetrics.values());
404
+ }
405
+
406
+ getAlerts(since?: number): Alert[] {
407
+ if (since) {
408
+ return this.alerts.filter(a => a.timestamp > since);
409
+ }
410
+ return this.alerts;
411
+ }
412
+
413
+ getHistoricalMetrics(hours: number = 1): SystemMetrics[] {
414
+ const since = Date.now() - (hours * 60 * 60 * 1000);
415
+ return this.systemMetrics.filter(m => m.timestamp > since);
416
+ }
417
+
418
+ // Summary statistics
419
+ getSummary(): {
420
+ uptime: number;
421
+ totalAgents: number;
422
+ activeAgents: number;
423
+ totalTasks: number;
424
+ completedTasks: number;
425
+ failedTasks: number;
426
+ successRate: number;
427
+ averageDuration: number;
428
+ currentThroughput: number;
429
+ alerts: number;
430
+ } {
431
+ const current = this.getSystemMetrics();
432
+ const uptime = Date.now() - this.startTime;
433
+ const totalAgents = this.agentMetrics.size;
434
+ const activeAgents = current?.activeAgents || 0;
435
+ const totalTasks = current?.totalTasks || 0;
436
+ const completedTasks = current?.completedTasks || 0;
437
+ const failedTasks = current?.failedTasks || 0;
438
+ const successRate = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
439
+ const averageDuration = current?.averageTaskDuration || 0;
440
+ const currentThroughput = current?.throughput || 0;
441
+ const alerts = this.alerts.filter(a => a.timestamp > Date.now() - 3600000).length; // Last hour
442
+
443
+ return {
444
+ uptime,
445
+ totalAgents,
446
+ activeAgents,
447
+ totalTasks,
448
+ completedTasks,
449
+ failedTasks,
450
+ successRate,
451
+ averageDuration,
452
+ currentThroughput,
453
+ alerts
454
+ };
455
+ }
456
+
457
+ // Export monitoring data
458
+ async exportMetrics(filepath: string): Promise<void> {
459
+ const data = {
460
+ summary: this.getSummary(),
461
+ systemMetrics: this.systemMetrics,
462
+ agentMetrics: Array.from(this.agentMetrics.values()),
463
+ alerts: this.alerts,
464
+ exported: new Date().toISOString()
465
+ };
466
+
467
+ await fs.writeFile(filepath, JSON.stringify(data, null, 2));
468
+ this.logger.info(`Exported metrics to ${filepath}`);
469
+ }
470
+ }
471
+
472
+ // Export types for external use
473
+ export type { AgentMetrics, SystemMetrics, Alert, MonitoringConfig };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * MCP tools for swarm orchestration
3
+ */
4
+
5
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
6
+ import { spawnSwarmAgent, getSwarmState } from '../cli/commands/swarm-spawn.ts';
7
+
8
+ /**
9
+ * Dispatch agent tool for swarm orchestration
10
+ */
11
+ export const dispatchAgentTool: Tool = {
12
+ name: 'dispatch_agent',
13
+ description: 'Spawn a new agent in the swarm to handle a specific task',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ type: {
18
+ type: 'string',
19
+ enum: ['researcher', 'developer', 'analyst', 'reviewer', 'coordinator'],
20
+ description: 'The type of agent to spawn',
21
+ },
22
+ task: {
23
+ type: 'string',
24
+ description: 'The specific task for the agent to complete',
25
+ },
26
+ name: {
27
+ type: 'string',
28
+ description: 'Optional name for the agent',
29
+ },
30
+ },
31
+ required: ['type', 'task'],
32
+ },
33
+ };
34
+
35
+ /**
36
+ * Handle dispatch agent tool execution
37
+ */
38
+ export async function handleDispatchAgent(args: any): Promise<any> {
39
+ const { type, task, name } = args;
40
+
41
+ // Get swarm ID from environment
42
+ const swarmId = Deno.env.get('CLAUDE_SWARM_ID');
43
+ if (!swarmId) {
44
+ throw new Error('Not running in swarm context');
45
+ }
46
+
47
+ // Get parent agent ID if available
48
+ const parentId = Deno.env.get('CLAUDE_SWARM_AGENT_ID');
49
+
50
+ try {
51
+ // Spawn the agent
52
+ const agent = await spawnSwarmAgent(swarmId, type, task, parentId);
53
+
54
+ return {
55
+ success: true,
56
+ agentId: agent.id,
57
+ agentName: agent.name,
58
+ terminalId: agent.terminalId,
59
+ message: `Successfully spawned ${agent.name} to work on: ${task}`,
60
+ };
61
+ } catch (error) {
62
+ return {
63
+ success: false,
64
+ error: error instanceof Error ? error.message : 'Unknown error',
65
+ };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Memory store tool for swarm coordination
71
+ */
72
+ export const memoryStoreTool: Tool = {
73
+ name: 'memory_store',
74
+ description: 'Store data in the shared swarm memory for coordination',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ key: {
79
+ type: 'string',
80
+ description: 'The key to store data under',
81
+ },
82
+ value: {
83
+ type: 'object',
84
+ description: 'The data to store (JSON object)',
85
+ },
86
+ },
87
+ required: ['key', 'value'],
88
+ },
89
+ };
90
+
91
+ /**
92
+ * Memory retrieve tool for swarm coordination
93
+ */
94
+ export const memoryRetrieveTool: Tool = {
95
+ name: 'memory_retrieve',
96
+ description: 'Retrieve data from the shared swarm memory',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ key: {
101
+ type: 'string',
102
+ description: 'The key to retrieve data from',
103
+ },
104
+ },
105
+ required: ['key'],
106
+ },
107
+ };
108
+
109
+ /**
110
+ * Swarm status tool
111
+ */
112
+ export const swarmStatusTool: Tool = {
113
+ name: 'swarm_status',
114
+ description: 'Get the current status of the swarm and all agents',
115
+ inputSchema: {
116
+ type: 'object',
117
+ properties: {},
118
+ },
119
+ };
120
+
121
+ /**
122
+ * Handle swarm status tool execution
123
+ */
124
+ export async function handleSwarmStatus(args: any): Promise<any> {
125
+ const swarmId = Deno.env.get('CLAUDE_SWARM_ID');
126
+ if (!swarmId) {
127
+ throw new Error('Not running in swarm context');
128
+ }
129
+
130
+ const state = getSwarmState(swarmId);
131
+ if (!state) {
132
+ throw new Error('Swarm state not found');
133
+ }
134
+
135
+ const agents = Array.from(state.agents.values()).map(agent => ({
136
+ id: agent.id,
137
+ type: agent.type,
138
+ name: agent.name,
139
+ task: agent.task,
140
+ status: agent.status,
141
+ parentId: agent.parentId,
142
+ }));
143
+
144
+ const runtime = Math.floor((Date.now() - state.startTime) / 1000);
145
+
146
+ return {
147
+ swarmId: state.swarmId,
148
+ objective: state.objective,
149
+ runtime: `${runtime}s`,
150
+ totalAgents: agents.length,
151
+ activeAgents: agents.filter(a => a.status === 'active').length,
152
+ completedAgents: agents.filter(a => a.status === 'completed').length,
153
+ failedAgents: agents.filter(a => a.status === 'failed').length,
154
+ agents,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Export all swarm tools
160
+ */
161
+ export const swarmTools = [
162
+ dispatchAgentTool,
163
+ memoryStoreTool,
164
+ memoryRetrieveTool,
165
+ swarmStatusTool,
166
+ ];
@@ -4,6 +4,20 @@
4
4
 
5
5
  // Utility helper functions
6
6
 
7
+ /**
8
+ * Simple calculator function that adds two numbers
9
+ */
10
+ export function add(a: number, b: number): number {
11
+ return a + b;
12
+ }
13
+
14
+ /**
15
+ * Simple hello world function
16
+ */
17
+ export function helloWorld(): string {
18
+ return "Hello, World!";
19
+ }
20
+
7
21
  /**
8
22
  * Generates a unique identifier
9
23
  */
@@ -408,6 +422,34 @@ export interface CircuitBreaker {
408
422
  reset(): void;
409
423
  }
410
424
 
425
+ /**
426
+ * Simple calculator function with basic operations
427
+ */
428
+ export function calculator(a: number, b: number, operation: '+' | '-' | '*' | '/' | '^' | '%'): number {
429
+ switch (operation) {
430
+ case '+':
431
+ return a + b;
432
+ case '-':
433
+ return a - b;
434
+ case '*':
435
+ return a * b;
436
+ case '/':
437
+ if (b === 0) {
438
+ throw new Error('Division by zero');
439
+ }
440
+ return a / b;
441
+ case '^':
442
+ return Math.pow(a, b);
443
+ case '%':
444
+ if (b === 0) {
445
+ throw new Error('Modulo by zero');
446
+ }
447
+ return a % b;
448
+ default:
449
+ throw new Error(`Invalid operation: ${operation}`);
450
+ }
451
+ }
452
+
411
453
  /**
412
454
  * Creates a circuit breaker
413
455
  */
@@ -473,4 +515,5 @@ export function circuitBreaker(
473
515
  state.state = 'closed';
474
516
  },
475
517
  };
476
- }
518
+ }
519
+