stigmergy 1.3.7 → 1.3.9
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/package.json
CHANGED
package/src/cli/router-beta.js
CHANGED
package/src/utils.js
CHANGED
|
@@ -699,7 +699,14 @@ async function executeCommand(command, args = [], options = {}) {
|
|
|
699
699
|
});
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
+
let timeoutId = null;
|
|
703
|
+
|
|
702
704
|
child.on('close', (code) => {
|
|
705
|
+
// Clear timeout if it exists
|
|
706
|
+
if (timeoutId) {
|
|
707
|
+
clearTimeout(timeoutId);
|
|
708
|
+
}
|
|
709
|
+
|
|
703
710
|
resolve({
|
|
704
711
|
code,
|
|
705
712
|
stdout,
|
|
@@ -709,6 +716,11 @@ async function executeCommand(command, args = [], options = {}) {
|
|
|
709
716
|
});
|
|
710
717
|
|
|
711
718
|
child.on('error', (error) => {
|
|
719
|
+
// Clear timeout if it exists
|
|
720
|
+
if (timeoutId) {
|
|
721
|
+
clearTimeout(timeoutId);
|
|
722
|
+
}
|
|
723
|
+
|
|
712
724
|
// Provide more detailed error information
|
|
713
725
|
let errorMessage = error.message;
|
|
714
726
|
if (error.code === 'ENOENT') {
|
|
@@ -729,7 +741,7 @@ async function executeCommand(command, args = [], options = {}) {
|
|
|
729
741
|
|
|
730
742
|
// Handle timeout
|
|
731
743
|
if (opts.timeout) {
|
|
732
|
-
setTimeout(() => {
|
|
744
|
+
timeoutId = setTimeout(() => {
|
|
733
745
|
child.kill();
|
|
734
746
|
reject({
|
|
735
747
|
error: new Error('Command timeout'),
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge Cases and Error Handling Tests: Smart Routing
|
|
5
|
+
* Tests edge cases and error handling scenarios for the SmartRouter class
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SmartRouter = require('../src/core/smart_router');
|
|
9
|
+
|
|
10
|
+
// Mock CLI_TOOLS to avoid actual CLI detection during testing
|
|
11
|
+
jest.mock('../src/core/cli_tools', () => ({
|
|
12
|
+
CLI_TOOLS: {
|
|
13
|
+
claude: { name: 'Claude CLI', command: 'claude', version: 'claude --version' },
|
|
14
|
+
qwen: { name: 'Qwen CLI', command: 'qwen', version: 'qwen --version' },
|
|
15
|
+
gemini: { name: 'Gemini CLI', command: 'gemini', version: 'gemini --version' },
|
|
16
|
+
iflow: { name: 'iFlow CLI', command: 'iflow', version: 'iflow --version' },
|
|
17
|
+
codebuddy: { name: 'CodeBuddy CLI', command: 'codebuddy', version: 'codebuddy --version' },
|
|
18
|
+
codex: { name: 'Codex CLI', command: 'codex', version: 'codex --version' },
|
|
19
|
+
qodercli: { name: 'QoderCLI', command: 'qodercli', version: 'qodercli --version' },
|
|
20
|
+
copilot: { name: 'Copilot CLI', command: 'copilot', version: 'copilot --version' },
|
|
21
|
+
kode: { name: 'Kode CLI', command: 'kode', version: 'kode --version' }
|
|
22
|
+
},
|
|
23
|
+
validateCLITool: jest.fn().mockReturnValue(true)
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock CLIHelpAnalyzer
|
|
27
|
+
jest.mock('../src/core/cli_help_analyzer', () => {
|
|
28
|
+
return jest.fn().mockImplementation(() => {
|
|
29
|
+
return {
|
|
30
|
+
setCLITools: jest.fn(),
|
|
31
|
+
initialize: jest.fn().mockResolvedValue(undefined),
|
|
32
|
+
analyzeCLI: jest.fn().mockResolvedValue({
|
|
33
|
+
success: true,
|
|
34
|
+
cliName: 'test-cli',
|
|
35
|
+
patterns: {
|
|
36
|
+
commands: [],
|
|
37
|
+
subcommands: []
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
loadPersistentConfig: jest.fn().mockResolvedValue({
|
|
41
|
+
failedAttempts: {},
|
|
42
|
+
cliPatterns: {}
|
|
43
|
+
}),
|
|
44
|
+
getCachedAnalysis: jest.fn().mockResolvedValue(null),
|
|
45
|
+
isCacheExpired: jest.fn().mockReturnValue(false),
|
|
46
|
+
generateOptimizedCall: jest.fn().mockReturnValue({
|
|
47
|
+
optimizedPrompt: 'test prompt'
|
|
48
|
+
}),
|
|
49
|
+
getAgentSkillCompatibilityScore: jest.fn().mockReturnValue({ score: 0.5, reasons: ['test'] }),
|
|
50
|
+
getEnhancedCLIPattern: jest.fn().mockResolvedValue({})
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Smart Routing Edge Cases and Error Handling', () => {
|
|
56
|
+
let router;
|
|
57
|
+
|
|
58
|
+
beforeEach(async () => {
|
|
59
|
+
router = new SmartRouter();
|
|
60
|
+
await router.initialize();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('should handle invalid input types gracefully', async () => {
|
|
64
|
+
// Test with numbers
|
|
65
|
+
let result = await router.smartRoute(123);
|
|
66
|
+
expect(result.tool).toBe('claude'); // Should default to default tool
|
|
67
|
+
expect(result.prompt).toBe('123');
|
|
68
|
+
|
|
69
|
+
// Test with objects
|
|
70
|
+
result = await router.smartRoute({ message: 'test' });
|
|
71
|
+
expect(result.tool).toBe('claude');
|
|
72
|
+
expect(result.prompt).toBe('[object Object]');
|
|
73
|
+
|
|
74
|
+
// Test with arrays
|
|
75
|
+
result = await router.smartRoute(['test', 'array']);
|
|
76
|
+
expect(result.tool).toBe('claude');
|
|
77
|
+
expect(result.prompt).toBe('test,array');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should handle very long input strings', async () => {
|
|
81
|
+
const longInput = 'a'.repeat(10000) + ' use claude to analyze this';
|
|
82
|
+
const result = await router.smartRoute(longInput);
|
|
83
|
+
expect(result.tool).toBe('claude');
|
|
84
|
+
// The prompt should contain the meaningful part of the input
|
|
85
|
+
expect(result.prompt).toContain('analyze this');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should handle input with special characters and unicode', async () => {
|
|
89
|
+
const specialInput = '使用claude分析这段包含特殊字符的代码: !@#$%^&*()中文测试';
|
|
90
|
+
const result = await router.smartRoute(specialInput);
|
|
91
|
+
expect(result.tool).toBe('claude');
|
|
92
|
+
expect(result.prompt).toBe('分析这段包含特殊字符的代码: !@#$%^&*()中文测试');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should handle multiple tool mentions and choose appropriately', async () => {
|
|
96
|
+
const input = 'use claude and qwen and gemini to analyze this code';
|
|
97
|
+
const result = await router.smartRoute(input);
|
|
98
|
+
// Should pick the first matching tool
|
|
99
|
+
expect(['claude', 'qwen', 'gemini']).toContain(result.tool);
|
|
100
|
+
expect(result.prompt).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should handle conflicting routing patterns', async () => {
|
|
104
|
+
const input = 'please use claude to write code with qwen';
|
|
105
|
+
const result = await router.smartRoute(input);
|
|
106
|
+
// Should handle the primary routing intent
|
|
107
|
+
expect(['claude', 'qwen']).toContain(result.tool);
|
|
108
|
+
expect(result.prompt).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('should handle non-existent tools gracefully', async () => {
|
|
112
|
+
// Test with a tool that doesn't exist in the system
|
|
113
|
+
const input = 'use non-existent-tool to do something';
|
|
114
|
+
const result = await router.smartRoute(input);
|
|
115
|
+
// Should default to default tool
|
|
116
|
+
expect(result.tool).toBe('claude');
|
|
117
|
+
expect(result.prompt).toBe('use non-existent-tool to do something');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should handle tools with similar names correctly', async () => {
|
|
121
|
+
// Test tools that might have similar names or keywords
|
|
122
|
+
const input = 'use codex and codebuddy to help with code';
|
|
123
|
+
const result = await router.smartRoute(input);
|
|
124
|
+
// Should match the first appropriate tool
|
|
125
|
+
expect(['codex', 'codebuddy']).toContain(result.tool);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should handle routing when CLI analysis fails', async () => {
|
|
129
|
+
// Mock a failure in CLI analysis
|
|
130
|
+
const originalGetOptimizedCLIPattern = router.getOptimizedCLIPattern;
|
|
131
|
+
router.getOptimizedCLIPattern = jest.fn().mockRejectedValue(new Error('CLI analysis failed'));
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const result = await router.smartRoute('analyze this code with claude');
|
|
135
|
+
// Should still return a valid result even if CLI analysis fails
|
|
136
|
+
expect(result.tool).toBe('claude');
|
|
137
|
+
expect(result.prompt).toBe('analyze this code with claude');
|
|
138
|
+
} finally {
|
|
139
|
+
// Restore original method
|
|
140
|
+
router.getOptimizedCLIPattern = originalGetOptimizedCLIPattern;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('should handle routing when validation fails', async () => {
|
|
145
|
+
// Mock a validation failure
|
|
146
|
+
const originalValidateCLITool = require('../src/core/cli_tools').validateCLITool;
|
|
147
|
+
require('../src/core/cli_tools').validateCLITool.mockReturnValue(false);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const result = await router.smartRoute('use claude to analyze');
|
|
151
|
+
// Should still work with default fallback
|
|
152
|
+
expect(result.tool).toBe('claude');
|
|
153
|
+
} finally {
|
|
154
|
+
// Restore original validation
|
|
155
|
+
require('../src/core/cli_tools').validateCLITool.mockReturnValue(true);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should handle enhanced routing when compatibility scoring fails', async () => {
|
|
160
|
+
const originalGetAgentSkillCompatibilityScore = router.getAgentSkillCompatibilityScore;
|
|
161
|
+
|
|
162
|
+
// Mock a failure in compatibility scoring
|
|
163
|
+
router.getAgentSkillCompatibilityScore = jest.fn().mockRejectedValue(new Error('Scoring failed'));
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const result = await router.smartRouteEnhanced('use claude to analyze');
|
|
167
|
+
// Should still return a result with fallback scoring
|
|
168
|
+
expect(result.tool).toBe('claude');
|
|
169
|
+
expect(result.prompt).toBe('use claude to analyze');
|
|
170
|
+
expect(result.confidence).toBeDefined();
|
|
171
|
+
} finally {
|
|
172
|
+
// Restore original method
|
|
173
|
+
router.getAgentSkillCompatibilityScore = originalGetAgentSkillCompatibilityScore;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should handle fallback routing generation properly', async () => {
|
|
178
|
+
// Test the fallback generation method directly
|
|
179
|
+
const primaryRoute = { tool: 'claude', prompt: 'analyze this code' };
|
|
180
|
+
const error = new Error('Primary route failed');
|
|
181
|
+
|
|
182
|
+
const fallbacks = await router.generateFallbackRoutes(primaryRoute, error);
|
|
183
|
+
|
|
184
|
+
// Should return an array of fallback routes
|
|
185
|
+
expect(Array.isArray(fallbacks)).toBe(true);
|
|
186
|
+
|
|
187
|
+
// Each fallback should have the required properties
|
|
188
|
+
for (const fallback of fallbacks) {
|
|
189
|
+
expect(fallback.tool).toBeDefined();
|
|
190
|
+
expect(fallback.prompt).toBe(primaryRoute.prompt);
|
|
191
|
+
expect(fallback.confidence).toBeDefined();
|
|
192
|
+
expect(fallback.compatibility).toBeDefined();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('should handle execution with multiple retries', async () => {
|
|
197
|
+
// Test the executeEnhancedRoute method with retry logic
|
|
198
|
+
// This is a complex test that verifies the retry mechanism works
|
|
199
|
+
const originalSmartRouteEnhanced = router.smartRouteEnhanced;
|
|
200
|
+
|
|
201
|
+
// Mock the method to fail the first time and succeed the second time
|
|
202
|
+
let callCount = 0;
|
|
203
|
+
router.smartRouteEnhanced = jest.fn().mockImplementation((input) => {
|
|
204
|
+
callCount++;
|
|
205
|
+
if (callCount === 1) {
|
|
206
|
+
throw new Error('First attempt failed');
|
|
207
|
+
}
|
|
208
|
+
return originalSmartRouteEnhanced.call(router, input);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const result = await router.executeEnhancedRoute('use claude to analyze');
|
|
213
|
+
// Should succeed after retry
|
|
214
|
+
expect(result.tool).toBe('claude');
|
|
215
|
+
} finally {
|
|
216
|
+
// Restore original method
|
|
217
|
+
router.smartRouteEnhanced = originalSmartRouteEnhanced;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('should handle cache expiration properly', async () => {
|
|
222
|
+
// Test the isRecentFailure method with various timestamps
|
|
223
|
+
const now = new Date();
|
|
224
|
+
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); // Exactly 1 hour ago
|
|
225
|
+
const fiftyNineMinsAgo = new Date(now.getTime() - 59 * 60 * 1000); // 59 minutes ago
|
|
226
|
+
const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000); // 2 hours ago
|
|
227
|
+
|
|
228
|
+
// 59 minutes ago should be considered recent (less than 1 hour)
|
|
229
|
+
expect(router.isRecentFailure(fiftyNineMinsAgo.toISOString())).toBe(true);
|
|
230
|
+
|
|
231
|
+
// 1 hour ago should not be considered recent (equal to 1 hour)
|
|
232
|
+
expect(router.isRecentFailure(oneHourAgo.toISOString())).toBe(false);
|
|
233
|
+
|
|
234
|
+
// 2 hours ago should not be considered recent
|
|
235
|
+
expect(router.isRecentFailure(twoHoursAgo.toISOString())).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('should handle unusual input patterns', async () => {
|
|
239
|
+
const unusualInputs = [
|
|
240
|
+
'CLAUDE analyze this', // Uppercase
|
|
241
|
+
' use claude to analyze ', // Extra spaces
|
|
242
|
+
'use\nclaude\nto\nanalyze', // Newlines
|
|
243
|
+
'use\tclaude\tto\tanalyze', // Tabs
|
|
244
|
+
'use claude to analyze', // Multiple spaces
|
|
245
|
+
'claude\'s analysis needed', // Possessive form
|
|
246
|
+
'about claude and his analysis', // Reference to tool
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
for (const input of unusualInputs) {
|
|
250
|
+
const result = await router.smartRoute(input);
|
|
251
|
+
// Should handle all these inputs gracefully
|
|
252
|
+
expect(result.tool).toBeDefined();
|
|
253
|
+
expect(result.prompt).toBeDefined();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('should handle empty or invalid CLI patterns', async () => {
|
|
258
|
+
const originalGetOptimizedCLIPattern = router.getOptimizedCLIPattern;
|
|
259
|
+
|
|
260
|
+
// Mock to return null/empty patterns
|
|
261
|
+
router.getOptimizedCLIPattern = jest.fn().mockResolvedValue(null);
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const result = await router.smartRoute('use claude to analyze');
|
|
265
|
+
// Should still work even with null CLI pattern
|
|
266
|
+
expect(result.tool).toBe('claude');
|
|
267
|
+
} finally {
|
|
268
|
+
// Restore original method
|
|
269
|
+
router.getOptimizedCLIPattern = originalGetOptimizedCLIPattern;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('should handle complex nested routing scenarios', async () => {
|
|
274
|
+
// Test complex scenario with multiple potential matches
|
|
275
|
+
const complexInput = 'Please use Claude (the best AI) to analyze this code, but if Claude is not available, try Qwen or Gemini';
|
|
276
|
+
const result = await router.smartRoute(complexInput);
|
|
277
|
+
|
|
278
|
+
// Should identify the primary intent (Claude)
|
|
279
|
+
expect(['claude', 'qwen', 'gemini']).toContain(result.tool);
|
|
280
|
+
expect(result.prompt).toBeDefined();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
console.log('Smart Routing Edge Cases and Error Handling Tests - All tests defined and ready to run');
|