myshell-tools 1.0.0 → 2.0.0-alpha.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.
Files changed (150) hide show
  1. package/CHANGELOG.md +44 -69
  2. package/LICENSE +21 -21
  3. package/README.md +178 -318
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +106 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/cost.d.ts +36 -0
  8. package/dist/commands/cost.js +103 -0
  9. package/dist/commands/cost.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +36 -0
  11. package/dist/commands/doctor.js +115 -0
  12. package/dist/commands/doctor.js.map +1 -0
  13. package/dist/core/assess.d.ts +25 -0
  14. package/dist/core/assess.js +142 -0
  15. package/dist/core/assess.js.map +1 -0
  16. package/dist/core/classify.d.ts +19 -0
  17. package/dist/core/classify.js +80 -0
  18. package/dist/core/classify.js.map +1 -0
  19. package/dist/core/escalate.d.ts +32 -0
  20. package/dist/core/escalate.js +57 -0
  21. package/dist/core/escalate.js.map +1 -0
  22. package/dist/core/index.d.ts +13 -0
  23. package/dist/core/index.js +12 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/orchestrate.d.ts +42 -0
  26. package/dist/core/orchestrate.js +439 -0
  27. package/dist/core/orchestrate.js.map +1 -0
  28. package/dist/core/policy.d.ts +9 -0
  29. package/dist/core/policy.js +27 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/prompt.d.ts +26 -0
  32. package/dist/core/prompt.js +125 -0
  33. package/dist/core/prompt.js.map +1 -0
  34. package/dist/core/review.d.ts +46 -0
  35. package/dist/core/review.js +148 -0
  36. package/dist/core/review.js.map +1 -0
  37. package/dist/core/route.d.ts +28 -0
  38. package/dist/core/route.js +52 -0
  39. package/dist/core/route.js.map +1 -0
  40. package/dist/core/types.d.ts +141 -0
  41. package/dist/core/types.js +14 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/infra/atomic.d.ts +53 -0
  44. package/dist/infra/atomic.js +171 -0
  45. package/dist/infra/atomic.js.map +1 -0
  46. package/dist/infra/clock.d.ts +9 -0
  47. package/dist/infra/clock.js +15 -0
  48. package/dist/infra/clock.js.map +1 -0
  49. package/dist/infra/index.d.ts +9 -0
  50. package/dist/infra/index.js +7 -0
  51. package/dist/infra/index.js.map +1 -0
  52. package/dist/infra/ledger.d.ts +49 -0
  53. package/dist/infra/ledger.js +90 -0
  54. package/dist/infra/ledger.js.map +1 -0
  55. package/dist/infra/paths.d.ts +28 -0
  56. package/dist/infra/paths.js +38 -0
  57. package/dist/infra/paths.js.map +1 -0
  58. package/dist/infra/pricing.d.ts +47 -0
  59. package/dist/infra/pricing.js +151 -0
  60. package/dist/infra/pricing.js.map +1 -0
  61. package/dist/infra/session.d.ts +28 -0
  62. package/dist/infra/session.js +61 -0
  63. package/dist/infra/session.js.map +1 -0
  64. package/dist/interface/render.d.ts +27 -0
  65. package/dist/interface/render.js +134 -0
  66. package/dist/interface/render.js.map +1 -0
  67. package/dist/interface/repl.d.ts +23 -0
  68. package/dist/interface/repl.js +90 -0
  69. package/dist/interface/repl.js.map +1 -0
  70. package/dist/interface/run.d.ts +20 -0
  71. package/dist/interface/run.js +31 -0
  72. package/dist/interface/run.js.map +1 -0
  73. package/dist/providers/claude-parse.d.ts +24 -0
  74. package/dist/providers/claude-parse.js +113 -0
  75. package/dist/providers/claude-parse.js.map +1 -0
  76. package/dist/providers/claude.d.ts +45 -0
  77. package/dist/providers/claude.js +122 -0
  78. package/dist/providers/claude.js.map +1 -0
  79. package/dist/providers/codex-parse.d.ts +32 -0
  80. package/dist/providers/codex-parse.js +145 -0
  81. package/dist/providers/codex-parse.js.map +1 -0
  82. package/dist/providers/codex.d.ts +44 -0
  83. package/dist/providers/codex.js +124 -0
  84. package/dist/providers/codex.js.map +1 -0
  85. package/dist/providers/detect.d.ts +49 -0
  86. package/dist/providers/detect.js +125 -0
  87. package/dist/providers/detect.js.map +1 -0
  88. package/dist/providers/errors.d.ts +49 -0
  89. package/dist/providers/errors.js +189 -0
  90. package/dist/providers/errors.js.map +1 -0
  91. package/dist/providers/index.d.ts +9 -0
  92. package/dist/providers/index.js +7 -0
  93. package/dist/providers/index.js.map +1 -0
  94. package/dist/providers/port.d.ts +74 -0
  95. package/dist/providers/port.js +16 -0
  96. package/dist/providers/port.js.map +1 -0
  97. package/dist/providers/registry.d.ts +21 -0
  98. package/dist/providers/registry.js +34 -0
  99. package/dist/providers/registry.js.map +1 -0
  100. package/dist/ui/banner.d.ts +19 -0
  101. package/dist/ui/banner.js +32 -0
  102. package/dist/ui/banner.js.map +1 -0
  103. package/dist/ui/spinner.d.ts +27 -0
  104. package/dist/ui/spinner.js +67 -0
  105. package/dist/ui/spinner.js.map +1 -0
  106. package/dist/ui/theme.d.ts +32 -0
  107. package/dist/ui/theme.js +56 -0
  108. package/dist/ui/theme.js.map +1 -0
  109. package/package.json +55 -49
  110. package/data/orchestrator.json +0 -113
  111. package/src/auth/recovery.mjs +0 -328
  112. package/src/auth/refresh.mjs +0 -373
  113. package/src/chef.mjs +0 -348
  114. package/src/cli/doctor.mjs +0 -568
  115. package/src/cli/reset.mjs +0 -447
  116. package/src/cli/status.mjs +0 -379
  117. package/src/cli.mjs +0 -429
  118. package/src/commands/doctor.mjs +0 -375
  119. package/src/commands/help.mjs +0 -324
  120. package/src/commands/status.mjs +0 -331
  121. package/src/monitor/health.mjs +0 -486
  122. package/src/monitor/performance.mjs +0 -442
  123. package/src/monitor/report.mjs +0 -535
  124. package/src/orchestrator/classify.mjs +0 -391
  125. package/src/orchestrator/confidence.mjs +0 -151
  126. package/src/orchestrator/handoffs.mjs +0 -231
  127. package/src/orchestrator/review.mjs +0 -222
  128. package/src/providers/balance.mjs +0 -201
  129. package/src/providers/claude.mjs +0 -236
  130. package/src/providers/codex.mjs +0 -255
  131. package/src/providers/detect.mjs +0 -185
  132. package/src/providers/errors.mjs +0 -373
  133. package/src/providers/select.mjs +0 -162
  134. package/src/repl-enhanced.mjs +0 -417
  135. package/src/repl.mjs +0 -321
  136. package/src/state/archive.mjs +0 -366
  137. package/src/state/atomic.mjs +0 -116
  138. package/src/state/cleanup.mjs +0 -440
  139. package/src/state/recovery.mjs +0 -461
  140. package/src/state/session.mjs +0 -147
  141. package/src/ui/errors.mjs +0 -456
  142. package/src/ui/formatter.mjs +0 -327
  143. package/src/ui/icons.mjs +0 -318
  144. package/src/ui/progress.mjs +0 -468
  145. package/templates/prompts/confidence-format.txt +0 -14
  146. package/templates/prompts/ic-with-feedback.txt +0 -41
  147. package/templates/prompts/ic.txt +0 -13
  148. package/templates/prompts/manager-review.txt +0 -40
  149. package/templates/prompts/manager.txt +0 -14
  150. package/templates/prompts/worker.txt +0 -12
@@ -1,373 +0,0 @@
1
- /**
2
- * errors.mjs — Comprehensive error handling and recovery for provider operations
3
- */
4
-
5
- import { spawnSync } from 'child_process';
6
- import { handleAuthFailure, getRecoverySuggestions } from '../auth/recovery.mjs';
7
-
8
- /**
9
- * Custom error class for CLI operations
10
- */
11
- export class CliError extends Error {
12
- constructor(message, details = {}) {
13
- super(message);
14
- this.name = 'CliError';
15
- this.details = details;
16
- this.isRecoverable = details.isRecoverable || false;
17
- this.provider = details.provider;
18
- this.command = details.command;
19
- this.args = details.args;
20
- this.exitCode = details.exitCode;
21
- this.stderr = details.stderr;
22
- this.originalError = details.originalError;
23
- }
24
- }
25
-
26
- /**
27
- * Detailed error for subprocess failures
28
- */
29
- export class DetailedCliError extends CliError {
30
- constructor(error, command, args, provider) {
31
- const details = {
32
- command,
33
- args: args?.slice() || [],
34
- provider,
35
- originalError: error,
36
- isRecoverable: isRecoverableError(error),
37
- exitCode: error.status || error.code || -1,
38
- stderr: error.stderr || error.message || ''
39
- };
40
-
41
- let message = `${provider?.toUpperCase() || 'CLI'} command failed`;
42
-
43
- if (command && args) {
44
- message += `: ${command} ${args.join(' ')}`;
45
- }
46
-
47
- if (error.message) {
48
- message += ` - ${error.message}`;
49
- }
50
-
51
- super(message, details);
52
- }
53
- }
54
-
55
- /**
56
- * Check if an error is recoverable with retries
57
- */
58
- export function isRecoverableError(error) {
59
- if (!error) return false;
60
-
61
- const errorString = error.toString().toLowerCase();
62
- const stderr = error.stderr?.toLowerCase() || '';
63
- const combined = errorString + ' ' + stderr;
64
-
65
- // Network-related errors (recoverable)
66
- if (combined.includes('timeout') ||
67
- combined.includes('network') ||
68
- combined.includes('enotfound') ||
69
- combined.includes('econnreset') ||
70
- combined.includes('econnrefused')) {
71
- return true;
72
- }
73
-
74
- // Temporary server issues (recoverable)
75
- if (combined.includes('502') ||
76
- combined.includes('503') ||
77
- combined.includes('504') ||
78
- combined.includes('internal server error')) {
79
- return true;
80
- }
81
-
82
- // Rate limiting (recoverable with delay)
83
- if (combined.includes('rate limit') ||
84
- combined.includes('429') ||
85
- combined.includes('too many requests')) {
86
- return true;
87
- }
88
-
89
- // Temporary auth token issues (recoverable with refresh)
90
- if (combined.includes('token expired') ||
91
- combined.includes('invalid token') ||
92
- combined.includes('token not found')) {
93
- return true;
94
- }
95
-
96
- // Process spawning issues (sometimes recoverable)
97
- if (error.code === 'EAGAIN' || error.code === 'EMFILE') {
98
- return true;
99
- }
100
-
101
- return false;
102
- }
103
-
104
- /**
105
- * Sleep with jitter for backoff
106
- */
107
- function sleep(ms) {
108
- const jitter = Math.floor(Math.random() * (ms * 0.1)); // 10% jitter
109
- return new Promise(resolve => setTimeout(resolve, ms + jitter));
110
- }
111
-
112
- /**
113
- * Robust subprocess execution with retry and recovery
114
- */
115
- export async function executeWithRecovery(command, args, options = {}) {
116
- const {
117
- provider,
118
- maxRetries = 3,
119
- timeoutMs = 120000,
120
- backoffMs = [1000, 2000, 4000],
121
- cwd = process.cwd(),
122
- onRetry
123
- } = options;
124
-
125
- let lastError;
126
-
127
- for (let attempt = 0; attempt < maxRetries; attempt++) {
128
- try {
129
- const proc = spawnSync(command, args, {
130
- encoding: 'utf8',
131
- stdio: ['pipe', 'pipe', 'pipe'],
132
- timeout: timeoutMs,
133
- cwd,
134
- });
135
-
136
- // Success case
137
- if (proc.status === 0) {
138
- return proc;
139
- }
140
-
141
- // Create detailed error for failed command
142
- const error = new Error(`Process exited with code ${proc.status}`);
143
- error.status = proc.status;
144
- error.stderr = proc.stderr;
145
- lastError = new DetailedCliError(error, command, args, provider);
146
-
147
- // Check if this is an auth error
148
- if (isAuthError(proc.stderr)) {
149
- const recovery = await handleAuthFailure(provider, proc.stderr);
150
- if (recovery.recovered) {
151
- continue; // Retry after successful recovery
152
- }
153
- }
154
-
155
- // Check if recoverable
156
- if (!isRecoverableError(lastError)) {
157
- throw lastError;
158
- }
159
-
160
- // Backoff before retry
161
- if (attempt < maxRetries - 1) {
162
- const backoff = backoffMs[Math.min(attempt, backoffMs.length - 1)];
163
- if (onRetry) {
164
- onRetry(attempt + 1, lastError, backoff);
165
- }
166
- await sleep(backoff);
167
- }
168
-
169
- } catch (spawnError) {
170
- lastError = new DetailedCliError(spawnError, command, args, provider);
171
-
172
- // Handle specific spawn errors
173
- if (spawnError.code === 'ENOENT') {
174
- // CLI not found - not recoverable with retries
175
- throw lastError;
176
- }
177
-
178
- if (!isRecoverableError(spawnError) || attempt === maxRetries - 1) {
179
- throw lastError;
180
- }
181
-
182
- // Retry for recoverable spawn errors
183
- if (attempt < maxRetries - 1) {
184
- const backoff = backoffMs[Math.min(attempt, backoffMs.length - 1)];
185
- if (onRetry) {
186
- onRetry(attempt + 1, lastError, backoff);
187
- }
188
- await sleep(backoff);
189
- }
190
- }
191
- }
192
-
193
- throw lastError;
194
- }
195
-
196
- /**
197
- * Check if error is authentication-related
198
- */
199
- function isAuthError(stderr) {
200
- if (!stderr) return false;
201
- const lower = stderr.toLowerCase();
202
-
203
- return lower.includes('authentication') ||
204
- lower.includes('unauthorized') ||
205
- lower.includes('401') ||
206
- lower.includes('invalid credentials') ||
207
- lower.includes('login required') ||
208
- lower.includes('access denied') ||
209
- lower.includes('forbidden') ||
210
- lower.includes('token') && (lower.includes('expired') || lower.includes('invalid'));
211
- }
212
-
213
- /**
214
- * Check if error is a rate limit
215
- */
216
- function isRateLimitError(stderr) {
217
- if (!stderr) return false;
218
- const lower = stderr.toLowerCase();
219
-
220
- return lower.includes('rate limit') ||
221
- lower.includes('429') ||
222
- lower.includes('too many requests') ||
223
- lower.includes('quota') && lower.includes('exceeded');
224
- }
225
-
226
- /**
227
- * Parse and classify CLI output for better error handling
228
- */
229
- export function parseCliOutput(stdout, stderr, exitCode) {
230
- const result = {
231
- success: exitCode === 0,
232
- output: stdout || '',
233
- stderr: stderr || '',
234
- exitCode,
235
- error: null,
236
- errorType: null,
237
- isRecoverable: false,
238
- suggestions: []
239
- };
240
-
241
- if (exitCode !== 0 || stderr) {
242
- const errorText = stderr || 'Process failed';
243
- result.error = errorText;
244
- result.isRecoverable = isRecoverableError({ stderr, status: exitCode });
245
-
246
- // Classify error type
247
- if (isAuthError(stderr)) {
248
- result.errorType = 'auth';
249
- result.suggestions.push('Re-authenticate with the CLI');
250
- } else if (isRateLimitError(stderr)) {
251
- result.errorType = 'rate_limit';
252
- result.suggestions.push('Wait before retrying');
253
- result.suggestions.push('Consider using a different model tier');
254
- } else if (stderr.toLowerCase().includes('timeout')) {
255
- result.errorType = 'timeout';
256
- result.suggestions.push('Increase timeout or simplify the request');
257
- } else if (stderr.toLowerCase().includes('network')) {
258
- result.errorType = 'network';
259
- result.suggestions.push('Check internet connection');
260
- } else if (exitCode === 127 || stderr.toLowerCase().includes('command not found')) {
261
- result.errorType = 'cli_missing';
262
- result.suggestions.push('Install the required CLI tool');
263
- } else {
264
- result.errorType = 'unknown';
265
- result.suggestions = getRecoverySuggestions(stderr);
266
- }
267
- }
268
-
269
- return result;
270
- }
271
-
272
- /**
273
- * Create user-friendly error message
274
- */
275
- export function createFriendlyErrorMessage(error, provider) {
276
- let message = `❌ ${provider?.toUpperCase() || 'CLI'} Error`;
277
-
278
- if (error instanceof CliError) {
279
- switch (error.details.errorType || 'unknown') {
280
- case 'cli_missing':
281
- message += '\n\n📦 CLI not installed or not found in PATH';
282
- if (provider === 'claude') {
283
- message += '\n Install: pip install anthropic-cli';
284
- message += '\n Then: claude auth login';
285
- } else if (provider === 'codex') {
286
- message += '\n Install: npm install -g @openai/codex';
287
- message += '\n Then: codex login';
288
- }
289
- break;
290
-
291
- case 'auth':
292
- message += '\n\n🔐 Authentication failed';
293
- message += `\n Run: ${provider === 'claude' ? 'claude auth login' : 'codex login'}`;
294
- break;
295
-
296
- case 'rate_limit':
297
- message += '\n\n⏱️ Rate limit exceeded';
298
- message += '\n Wait a few minutes before retrying';
299
- message += '\n Consider using a different model tier';
300
- break;
301
-
302
- case 'timeout':
303
- message += '\n\n⏰ Request timed out';
304
- message += '\n Try simplifying your request or increasing timeout';
305
- break;
306
-
307
- case 'network':
308
- message += '\n\n🌐 Network connectivity issue';
309
- message += '\n Check your internet connection and try again';
310
- break;
311
-
312
- default:
313
- message += '\n\n💥 Unexpected error occurred';
314
- if (error.message) {
315
- message += `\n ${error.message}`;
316
- }
317
- }
318
-
319
- if (error.details.suggestions?.length > 0) {
320
- message += '\n\n💡 Suggestions:';
321
- for (const suggestion of error.details.suggestions) {
322
- message += `\n • ${suggestion}`;
323
- }
324
- }
325
-
326
- if (error.details.isRecoverable) {
327
- message += '\n\n🔄 This error might be temporary. Cortex will retry automatically.';
328
- }
329
-
330
- } else {
331
- message += `\n ${error.message || 'Unknown error occurred'}`;
332
- }
333
-
334
- return message;
335
- }
336
-
337
- /**
338
- * Graceful error handling wrapper for provider functions
339
- */
340
- export function withErrorHandling(fn, provider) {
341
- return async (...args) => {
342
- try {
343
- return await fn(...args);
344
- } catch (error) {
345
- if (error instanceof CliError) {
346
- throw error; // Already handled
347
- }
348
-
349
- // Convert generic errors to CLI errors
350
- const cliError = new CliError(error.message || 'Operation failed', {
351
- provider,
352
- originalError: error,
353
- isRecoverable: isRecoverableError(error)
354
- });
355
-
356
- throw cliError;
357
- }
358
- };
359
- }
360
-
361
- /**
362
- * Progress callback for retries
363
- */
364
- export function defaultRetryCallback(attempt, error, backoffMs) {
365
- const provider = error.provider?.toUpperCase() || 'CLI';
366
- console.log(`⚠️ ${provider} attempt ${attempt} failed, retrying in ${backoffMs}ms...`);
367
-
368
- if (error.details?.errorType === 'rate_limit') {
369
- console.log(' Rate limit detected, backing off...');
370
- } else if (error.details?.errorType === 'network') {
371
- console.log(' Network issue detected, retrying...');
372
- }
373
- }
@@ -1,162 +0,0 @@
1
- /**
2
- * select.mjs — Intelligent provider selection and load balancing
3
- */
4
-
5
- import { getRecentHandoffs, getHandoffStats } from '../orchestrator/handoffs.mjs';
6
-
7
- /**
8
- * Select the best provider for a task based on load balancing and context
9
- */
10
- export function selectProvider(tier, context = {}) {
11
- const { availableModels, sessionId } = context;
12
-
13
- if (!availableModels) {
14
- console.warn('No available models provided to selectProvider');
15
- return null;
16
- }
17
-
18
- const tierModels = availableModels[tier] || [];
19
- if (tierModels.length === 0) {
20
- return null;
21
- }
22
-
23
- // If only one provider available, use it
24
- if (tierModels.length === 1) {
25
- return tierModels[0];
26
- }
27
-
28
- // Get load balancing data
29
- const loadBalance = getProviderLoadBalance(sessionId);
30
- const modelStrengths = getModelStrengths(tier);
31
-
32
- // Score each available model
33
- const scoredModels = tierModels.map(model => {
34
- let score = 0;
35
-
36
- // Base score from model strength for this tier
37
- score += modelStrengths[model.provider] || 0.5;
38
-
39
- // Load balancing bonus (prefer less-used providers)
40
- const providerLoad = loadBalance[model.provider] || 0;
41
- const avgLoad = Object.values(loadBalance).reduce((sum, load) => sum + load, 0) / Object.keys(loadBalance).length || 0;
42
-
43
- if (providerLoad < avgLoad - 2) {
44
- score += 0.3; // Bonus for underused provider
45
- } else if (providerLoad > avgLoad + 2) {
46
- score -= 0.2; // Penalty for overused provider
47
- }
48
-
49
- // Prefer models that haven't failed recently
50
- const recentFailures = getRecentFailures(model.provider, 0.5); // Last 30 min
51
- score -= recentFailures * 0.1;
52
-
53
- return {
54
- ...model,
55
- score,
56
- load: providerLoad,
57
- failures: recentFailures
58
- };
59
- });
60
-
61
- // Sort by score (highest first) and return best
62
- scoredModels.sort((a, b) => b.score - a.score);
63
-
64
- const selected = scoredModels[0];
65
-
66
- console.log(` 🎯 Provider selection for ${tier}: ${selected.provider}/${selected.model} (score: ${selected.score.toFixed(2)})`);
67
-
68
- return selected;
69
- }
70
-
71
- /**
72
- * Get provider load balance statistics
73
- */
74
- function getProviderLoadBalance(sessionId, timeWindowHours = 1) {
75
- const handoffs = getRecentHandoffs(timeWindowHours, sessionId);
76
-
77
- const loadBalance = {};
78
-
79
- for (const handoff of handoffs) {
80
- if (handoff.provider_from) {
81
- loadBalance[handoff.provider_from] = (loadBalance[handoff.provider_from] || 0) + 1;
82
- }
83
- if (handoff.provider_to) {
84
- loadBalance[handoff.provider_to] = (loadBalance[handoff.provider_to] || 0) + 1;
85
- }
86
- }
87
-
88
- return loadBalance;
89
- }
90
-
91
- /**
92
- * Get model strengths by tier based on empirical performance
93
- */
94
- function getModelStrengths(tier) {
95
- const strengths = {
96
- worker: {
97
- claude: 0.8, // Excellent for search and analysis
98
- codex: 0.6 // Good for quick lookups
99
- },
100
- ic: {
101
- codex: 0.8, // Excellent for implementation
102
- claude: 0.7 // Good for code generation
103
- },
104
- manager: {
105
- claude: 0.9, // Excellent for architecture and review
106
- codex: 0.7 // Good for complex reasoning
107
- }
108
- };
109
-
110
- return strengths[tier] || { claude: 0.5, codex: 0.5 };
111
- }
112
-
113
- /**
114
- * Get recent failure count for a provider
115
- */
116
- function getRecentFailures(provider, timeWindowHours = 0.5) {
117
- const handoffs = getRecentHandoffs(timeWindowHours);
118
-
119
- return handoffs.filter(handoff =>
120
- handoff.provider_from === provider &&
121
- handoff.op === 'escalate_up' &&
122
- handoff.reason.includes('failure')
123
- ).length;
124
- }
125
-
126
- /**
127
- * Check if provider is currently available
128
- */
129
- export function checkProviderHealth(provider) {
130
- const recentFailures = getRecentFailures(provider, 0.25); // Last 15 minutes
131
-
132
- return {
133
- available: recentFailures < 3, // Healthy if fewer than 3 failures in 15 min
134
- failures: recentFailures,
135
- status: recentFailures >= 3 ? 'degraded' : 'healthy'
136
- };
137
- }
138
-
139
- /**
140
- * Get provider usage statistics for debugging
141
- */
142
- export function getProviderStats(timeWindowHours = 1) {
143
- const stats = getHandoffStats(timeWindowHours);
144
-
145
- const providerStats = {};
146
-
147
- for (const [provider, count] of Object.entries(stats.by_provider)) {
148
- providerStats[provider] = {
149
- handoffs: count,
150
- percentage: Math.round((count / stats.total) * 100),
151
- health: checkProviderHealth(provider)
152
- };
153
- }
154
-
155
- return {
156
- total_handoffs: stats.total,
157
- time_window_hours: timeWindowHours,
158
- providers: providerStats,
159
- avg_duration_ms: stats.avg_duration_ms,
160
- success_rate: Math.round(stats.success_rate * 100)
161
- };
162
- }