onbuzz 3.6.1 → 3.6.2
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 +1 -1
- package/src/__test-utils__/fixtures/malformedJson.js +31 -0
- package/src/__test-utils__/globalSetup.js +9 -0
- package/src/__test-utils__/globalTeardown.js +12 -0
- package/src/__test-utils__/mockFactories.js +101 -0
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
- package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
- package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
- package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
- package/src/core/__tests__/agentPool.test.js +601 -0
- package/src/core/__tests__/agentScheduler.test.js +576 -0
- package/src/core/__tests__/contextManager.test.js +252 -0
- package/src/core/__tests__/flowExecutor.test.js +262 -0
- package/src/core/__tests__/messageProcessor.test.js +627 -0
- package/src/core/__tests__/orchestrator.test.js +257 -0
- package/src/core/__tests__/stateManager.test.js +375 -0
- package/src/core/agentPool.js +11 -1
- package/src/index.js +25 -9
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
- package/src/services/__tests__/agentActivityService.test.js +319 -0
- package/src/services/__tests__/apiKeyManager.test.js +206 -0
- package/src/services/__tests__/benchmarkService.test.js +184 -0
- package/src/services/__tests__/budgetService.test.js +211 -0
- package/src/services/__tests__/contextInjectionService.test.js +205 -0
- package/src/services/__tests__/conversationCompactionService.test.js +280 -0
- package/src/services/__tests__/credentialVault.test.js +469 -0
- package/src/services/__tests__/errorHandler.test.js +314 -0
- package/src/services/__tests__/fileAttachmentService.test.js +278 -0
- package/src/services/__tests__/flowContextService.test.js +199 -0
- package/src/services/__tests__/memoryService.test.js +450 -0
- package/src/services/__tests__/modelRouterService.test.js +388 -0
- package/src/services/__tests__/modelsService.test.js +261 -0
- package/src/services/__tests__/portRegistry.test.js +123 -0
- package/src/services/__tests__/projectDetector.test.js +34 -0
- package/src/services/__tests__/promptService.test.js +242 -0
- package/src/services/__tests__/qualityInspector.test.js +97 -0
- package/src/services/__tests__/scheduleService.test.js +308 -0
- package/src/services/__tests__/serviceRegistry.test.js +74 -0
- package/src/services/__tests__/skillsService.test.js +402 -0
- package/src/services/__tests__/tokenCountingService.test.js +48 -0
- package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
- package/src/tools/__tests__/agentDelayTool.test.js +342 -0
- package/src/tools/__tests__/asyncToolManager.test.js +344 -0
- package/src/tools/__tests__/baseTool.test.js +420 -0
- package/src/tools/__tests__/codeMapTool.test.js +348 -0
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
- package/src/tools/__tests__/fileTreeTool.test.js +274 -0
- package/src/tools/__tests__/filesystemTool.test.js +717 -0
- package/src/tools/__tests__/helpTool.test.js +204 -0
- package/src/tools/__tests__/jobDoneTool.test.js +296 -0
- package/src/tools/__tests__/memoryTool.test.js +297 -0
- package/src/tools/__tests__/seekTool.test.js +282 -0
- package/src/tools/__tests__/skillsTool.test.js +226 -0
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
- package/src/tools/__tests__/taskManagerTool.test.js +725 -0
- package/src/tools/__tests__/terminalTool.test.js +384 -0
- package/src/tools/__tests__/userPromptTool.test.js +297 -0
- package/src/tools/__tests__/webTool.e2e.test.js +25 -11
- package/src/tools/webTool.js +6 -12
- package/src/types/__tests__/agent.test.js +499 -0
- package/src/types/__tests__/contextReference.test.js +606 -0
- package/src/types/__tests__/conversation.test.js +555 -0
- package/src/types/__tests__/toolCommand.test.js +584 -0
- package/src/types/contextReference.js +1 -1
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
- package/src/utilities/__tests__/configManager.test.js +397 -0
- package/src/utilities/__tests__/constants.test.js +49 -0
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
- package/src/utilities/__tests__/fileProcessor.test.js +104 -0
- package/src/utilities/__tests__/jsonRepair.test.js +104 -0
- package/src/utilities/__tests__/logger.test.js +129 -0
- package/src/utilities/__tests__/platformUtils.test.js +87 -0
- package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
- package/src/utilities/__tests__/tagParser.test.js +887 -0
- package/src/utilities/__tests__/toolConstants.test.js +94 -0
- package/src/utilities/tagParser.js +2 -2
- package/src/tools/browserTool.js +0 -897
- package/src/utilities/platformUtils.test.js +0 -98
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
const fsMock = {
|
|
4
|
+
readFile: jest.fn(),
|
|
5
|
+
writeFile: jest.fn().mockResolvedValue(undefined),
|
|
6
|
+
mkdir: jest.fn().mockResolvedValue(undefined),
|
|
7
|
+
rename: jest.fn().mockResolvedValue(undefined),
|
|
8
|
+
unlink: jest.fn().mockResolvedValue(undefined),
|
|
9
|
+
stat: jest.fn()
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
jest.unstable_mockModule('fs', () => ({
|
|
13
|
+
promises: fsMock
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.unstable_mockModule('../../utilities/userDataDir.js', () => ({
|
|
17
|
+
getUserDataPaths: jest.fn().mockReturnValue({
|
|
18
|
+
runtime: '/tmp/test-runtime',
|
|
19
|
+
config: '/tmp/test-config'
|
|
20
|
+
})
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const { PortRegistry, getPortRegistry } = await import('../portRegistry.js');
|
|
24
|
+
|
|
25
|
+
describe('PortRegistry', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
jest.clearAllMocks();
|
|
28
|
+
// Return empty registry by default (ENOENT simulates first run)
|
|
29
|
+
fsMock.readFile.mockRejectedValue(Object.assign(new Error('ENOENT'), { code: 'ENOENT' }));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('constructor creates instance', () => {
|
|
33
|
+
const registry = new PortRegistry();
|
|
34
|
+
expect(registry).toBeInstanceOf(PortRegistry);
|
|
35
|
+
expect(registry._initialized).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('getPortRegistry returns singleton (same instance)', () => {
|
|
39
|
+
// Note: singleton is module-scoped, so both calls return the same instance
|
|
40
|
+
const a = getPortRegistry();
|
|
41
|
+
const b = getPortRegistry();
|
|
42
|
+
expect(a).toBe(b);
|
|
43
|
+
expect(a).toBeInstanceOf(PortRegistry);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('registerService stores service info with port', async () => {
|
|
47
|
+
const registry = new PortRegistry();
|
|
48
|
+
const result = await registry.registerService('backend', { port: 3000 });
|
|
49
|
+
|
|
50
|
+
expect(result).toBeDefined();
|
|
51
|
+
expect(result.port).toBe(3000);
|
|
52
|
+
expect(result.host).toBe('localhost');
|
|
53
|
+
expect(result.protocol).toBe('http');
|
|
54
|
+
expect(fsMock.writeFile).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('getService returns registered service', async () => {
|
|
58
|
+
const registry = new PortRegistry();
|
|
59
|
+
|
|
60
|
+
// First register
|
|
61
|
+
await registry.registerService('api', { port: 8080 });
|
|
62
|
+
|
|
63
|
+
// Mock readFile to return the saved data
|
|
64
|
+
const savedData = JSON.stringify({
|
|
65
|
+
version: 1,
|
|
66
|
+
lastUpdated: new Date().toISOString(),
|
|
67
|
+
services: {
|
|
68
|
+
api: { port: 8080, host: 'localhost', protocol: 'http', pid: process.pid, startedAt: new Date().toISOString(), metadata: {} }
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
fsMock.readFile.mockResolvedValue(savedData);
|
|
72
|
+
|
|
73
|
+
const service = await registry.getService('api');
|
|
74
|
+
expect(service).toBeDefined();
|
|
75
|
+
expect(service.port).toBe(8080);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('getService returns null for unknown name', async () => {
|
|
79
|
+
const registry = new PortRegistry();
|
|
80
|
+
const service = await registry.getService('nonexistent');
|
|
81
|
+
expect(service).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('unregisterService removes service', async () => {
|
|
85
|
+
const registry = new PortRegistry();
|
|
86
|
+
|
|
87
|
+
// Mock a registry with a service
|
|
88
|
+
const savedData = JSON.stringify({
|
|
89
|
+
version: 1,
|
|
90
|
+
lastUpdated: new Date().toISOString(),
|
|
91
|
+
services: {
|
|
92
|
+
myservice: { port: 4000, host: 'localhost', protocol: 'http', pid: process.pid, startedAt: new Date().toISOString(), metadata: {} }
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
fsMock.readFile.mockResolvedValue(savedData);
|
|
96
|
+
|
|
97
|
+
const result = await registry.unregisterService('myservice');
|
|
98
|
+
expect(result).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('getServiceUrl builds URL from protocol/host/port', async () => {
|
|
102
|
+
const registry = new PortRegistry();
|
|
103
|
+
|
|
104
|
+
const savedData = JSON.stringify({
|
|
105
|
+
version: 1,
|
|
106
|
+
lastUpdated: new Date().toISOString(),
|
|
107
|
+
services: {
|
|
108
|
+
web: { port: 443, host: 'example.com', protocol: 'https', pid: process.pid, startedAt: new Date().toISOString(), metadata: {} }
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
fsMock.readFile.mockResolvedValue(savedData);
|
|
112
|
+
|
|
113
|
+
const url = await registry.getServiceUrl('web');
|
|
114
|
+
expect(url).toBe('https://example.com:443');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('isProcessRunning returns boolean (test with current process.pid)', () => {
|
|
118
|
+
const registry = new PortRegistry();
|
|
119
|
+
const running = registry.isProcessRunning(process.pid);
|
|
120
|
+
expect(typeof running).toBe('boolean');
|
|
121
|
+
expect(running).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import ProjectDetector, { getProjectDetector, PROJECT_TYPES } from '../projectDetector.js';
|
|
3
|
+
|
|
4
|
+
describe('ProjectDetector', () => {
|
|
5
|
+
test('constructor creates instance', () => {
|
|
6
|
+
const detector = new ProjectDetector();
|
|
7
|
+
expect(detector).toBeInstanceOf(ProjectDetector);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('getProjectDetector returns a ProjectDetector instance (singleton)', () => {
|
|
11
|
+
const detector1 = getProjectDetector();
|
|
12
|
+
const detector2 = getProjectDetector();
|
|
13
|
+
expect(detector1).toBeInstanceOf(ProjectDetector);
|
|
14
|
+
expect(detector1).toBe(detector2);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('PROJECT_TYPES has expected values', () => {
|
|
18
|
+
expect(PROJECT_TYPES.REACT_CRA).toBe('react-cra');
|
|
19
|
+
expect(PROJECT_TYPES.NEXTJS).toBe('nextjs');
|
|
20
|
+
expect(PROJECT_TYPES.STATIC_HTML).toBe('static-html');
|
|
21
|
+
expect(PROJECT_TYPES.UNKNOWN).toBe('unknown');
|
|
22
|
+
expect(PROJECT_TYPES.ANGULAR).toBe('angular');
|
|
23
|
+
expect(PROJECT_TYPES.VUE_VITE).toBe('vue-vite');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('detect returns object with projectType property', async () => {
|
|
27
|
+
const detector = new ProjectDetector();
|
|
28
|
+
const result = await detector.detect(process.cwd());
|
|
29
|
+
expect(result).toHaveProperty('projectType');
|
|
30
|
+
expect(result).toHaveProperty('projectDir');
|
|
31
|
+
expect(result).toHaveProperty('entryPoints');
|
|
32
|
+
expect(typeof result.projectType).toBe('string');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
const { PromptService, getPromptService } = await import('../promptService.js');
|
|
5
|
+
|
|
6
|
+
describe('PromptService', () => {
|
|
7
|
+
let service;
|
|
8
|
+
let logger;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.useFakeTimers();
|
|
12
|
+
logger = createMockLogger();
|
|
13
|
+
service = new PromptService(logger);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Don't call clearAll() — it rejects pending promises which crashes Node
|
|
18
|
+
// Instead just clear internal state directly
|
|
19
|
+
if (service.pendingRequests) service.pendingRequests.clear();
|
|
20
|
+
if (service.requestHistory) service.requestHistory.length = 0;
|
|
21
|
+
jest.useRealTimers();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('constructor initializes with empty state', () => {
|
|
25
|
+
expect(service.pendingRequests.size).toBe(0);
|
|
26
|
+
expect(service.requestHistory).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('_generateRequestId returns unique IDs', () => {
|
|
30
|
+
const id1 = service._generateRequestId();
|
|
31
|
+
const id2 = service._generateRequestId();
|
|
32
|
+
expect(id1).toMatch(/^prompt-/);
|
|
33
|
+
expect(id1).not.toBe(id2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('createPromptRequest creates a pending request with promise', () => {
|
|
37
|
+
const { requestInfo, promise } = service.createPromptRequest('agent-1', {
|
|
38
|
+
message: 'Choose an option',
|
|
39
|
+
questions: [{ message: 'Pick one', options: ['A', 'B'] }]
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(requestInfo.agentId).toBe('agent-1');
|
|
43
|
+
expect(requestInfo.questions).toHaveLength(1);
|
|
44
|
+
expect(requestInfo.message).toBe('Choose an option');
|
|
45
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
46
|
+
expect(service.pendingRequests.size).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('createPromptRequest normalizes questions with defaults', () => {
|
|
50
|
+
const { requestInfo } = service.createPromptRequest('agent-1', {
|
|
51
|
+
questions: [{ question: 'What color?', options: ['red', 'blue'] }]
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const q = requestInfo.questions[0];
|
|
55
|
+
expect(q.id).toBe('q1');
|
|
56
|
+
expect(q.message).toBe('What color?');
|
|
57
|
+
expect(q.options[0].label).toBe('red');
|
|
58
|
+
expect(q.allowFreeText).toBe(true);
|
|
59
|
+
expect(q.required).toBe(true);
|
|
60
|
+
expect(q.multiSelect).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('createPromptRequest normalizes option objects', () => {
|
|
64
|
+
const { requestInfo } = service.createPromptRequest('agent-1', {
|
|
65
|
+
questions: [{
|
|
66
|
+
message: 'Q',
|
|
67
|
+
options: [{ id: 'o1', label: 'Option 1', description: 'Desc' }]
|
|
68
|
+
}]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const opt = requestInfo.questions[0].options[0];
|
|
72
|
+
expect(opt.id).toBe('o1');
|
|
73
|
+
expect(opt.label).toBe('Option 1');
|
|
74
|
+
expect(opt.description).toBe('Desc');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('createPromptRequest wraps single prompt as questions array', () => {
|
|
78
|
+
const { requestInfo } = service.createPromptRequest('agent-1', {
|
|
79
|
+
message: 'Pick an option'
|
|
80
|
+
});
|
|
81
|
+
expect(requestInfo.questions).toHaveLength(1);
|
|
82
|
+
expect(requestInfo.questions[0].message).toBe('Pick an option');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('submitResponse resolves the promise and removes from pending', async () => {
|
|
86
|
+
const { requestInfo, promise } = service.createPromptRequest('agent-1', {
|
|
87
|
+
questions: [{ message: 'Q' }]
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const response = { answers: [{ questionId: 'q1', freeText: 'My answer' }] };
|
|
91
|
+
const result = service.submitResponse(requestInfo.requestId, response);
|
|
92
|
+
|
|
93
|
+
expect(result.success).toBe(true);
|
|
94
|
+
expect(service.pendingRequests.size).toBe(0);
|
|
95
|
+
|
|
96
|
+
const resolved = await promise;
|
|
97
|
+
expect(resolved.success).toBe(true);
|
|
98
|
+
expect(resolved.response).toBe(response);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('submitResponse returns error for unknown request', () => {
|
|
102
|
+
const result = service.submitResponse('unknown-id', {});
|
|
103
|
+
expect(result.success).toBe(false);
|
|
104
|
+
expect(result.error).toContain('not found');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('cancelRequest rejects the promise', async () => {
|
|
108
|
+
const { requestInfo, promise } = service.createPromptRequest('agent-1', {
|
|
109
|
+
questions: [{ message: 'Q' }]
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = service.cancelRequest(requestInfo.requestId, 'test cancel');
|
|
113
|
+
expect(result.success).toBe(true);
|
|
114
|
+
|
|
115
|
+
await expect(promise).rejects.toThrow('cancelled');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('cancelRequest returns error for unknown request', () => {
|
|
119
|
+
const result = service.cancelRequest('unknown-id');
|
|
120
|
+
expect(result.success).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('timeout rejects the promise', async () => {
|
|
124
|
+
const { requestInfo, promise } = service.createPromptRequest('agent-1', {
|
|
125
|
+
questions: [{ message: 'Q' }]
|
|
126
|
+
}, { timeout: 1000 });
|
|
127
|
+
|
|
128
|
+
jest.advanceTimersByTime(1500);
|
|
129
|
+
|
|
130
|
+
await expect(promise).rejects.toThrow('timed out');
|
|
131
|
+
expect(service.pendingRequests.size).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('timeout is no-op for already resolved request', () => {
|
|
135
|
+
const { requestInfo } = service.createPromptRequest('agent-1', {
|
|
136
|
+
questions: [{ message: 'Q' }]
|
|
137
|
+
});
|
|
138
|
+
service.submitResponse(requestInfo.requestId, { answers: [] });
|
|
139
|
+
|
|
140
|
+
// Timeout handler should not throw
|
|
141
|
+
service._handleTimeout(requestInfo.requestId);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('_addToHistory trims when exceeding max size', () => {
|
|
145
|
+
service.maxHistorySize = 3;
|
|
146
|
+
for (let i = 0; i < 5; i++) {
|
|
147
|
+
service._addToHistory({ id: i });
|
|
148
|
+
}
|
|
149
|
+
expect(service.requestHistory).toHaveLength(3);
|
|
150
|
+
expect(service.requestHistory[0].id).toBe(2);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('getPendingRequest returns info or null', () => {
|
|
154
|
+
const { requestInfo } = service.createPromptRequest('agent-1', {
|
|
155
|
+
questions: [{ message: 'Q' }]
|
|
156
|
+
});
|
|
157
|
+
expect(service.getPendingRequest(requestInfo.requestId)).toBeTruthy();
|
|
158
|
+
expect(service.getPendingRequest('unknown')).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('getPendingRequestsForAgent returns requests for agent', () => {
|
|
162
|
+
service.createPromptRequest('agent-1', { questions: [{ message: 'Q1' }] });
|
|
163
|
+
service.createPromptRequest('agent-1', { questions: [{ message: 'Q2' }] });
|
|
164
|
+
service.createPromptRequest('agent-2', { questions: [{ message: 'Q3' }] });
|
|
165
|
+
|
|
166
|
+
const results = service.getPendingRequestsForAgent('agent-1');
|
|
167
|
+
expect(results).toHaveLength(2);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('hasPendingPrompts returns true/false', () => {
|
|
171
|
+
expect(service.hasPendingPrompts('agent-1')).toBe(false);
|
|
172
|
+
service.createPromptRequest('agent-1', { questions: [{ message: 'Q' }] });
|
|
173
|
+
expect(service.hasPendingPrompts('agent-1')).toBe(true);
|
|
174
|
+
expect(service.hasPendingPrompts('agent-2')).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('getHistory returns last N records', () => {
|
|
178
|
+
for (let i = 0; i < 10; i++) {
|
|
179
|
+
service._addToHistory({ id: i });
|
|
180
|
+
}
|
|
181
|
+
const recent = service.getHistory(3);
|
|
182
|
+
expect(recent).toHaveLength(3);
|
|
183
|
+
expect(recent[0].id).toBe(7);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('clearAll rejects all pending and clears', async () => {
|
|
187
|
+
const { promise: p1 } = service.createPromptRequest('agent-1', { questions: [{ message: 'Q' }] });
|
|
188
|
+
const { promise: p2 } = service.createPromptRequest('agent-2', { questions: [{ message: 'Q' }] });
|
|
189
|
+
|
|
190
|
+
service.clearAll();
|
|
191
|
+
|
|
192
|
+
await expect(p1).rejects.toThrow('shutdown');
|
|
193
|
+
await expect(p2).rejects.toThrow('shutdown');
|
|
194
|
+
expect(service.pendingRequests.size).toBe(0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('formatResponseAsMessage formats answers', () => {
|
|
198
|
+
const requestInfo = {
|
|
199
|
+
message: 'Please answer:',
|
|
200
|
+
questions: [
|
|
201
|
+
{ id: 'q1', message: 'Color?', options: [{ id: 'o1', label: 'Red' }] }
|
|
202
|
+
]
|
|
203
|
+
};
|
|
204
|
+
const response = {
|
|
205
|
+
answers: [
|
|
206
|
+
{ questionId: 'q1', selectedOptions: ['o1'], freeText: 'I like red', webSearchRequested: true }
|
|
207
|
+
]
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const msg = service.formatResponseAsMessage(requestInfo, response);
|
|
211
|
+
expect(msg).toContain('**Context:** Please answer:');
|
|
212
|
+
expect(msg).toContain('**Q: Color?**');
|
|
213
|
+
expect(msg).toContain('Red');
|
|
214
|
+
expect(msg).toContain('"I like red"');
|
|
215
|
+
expect(msg).toContain('Web search');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('formatResponseAsMessage handles missing question match', () => {
|
|
219
|
+
const requestInfo = { questions: [] };
|
|
220
|
+
const response = {
|
|
221
|
+
answers: [{ questionId: 'q99', selectedOptions: [] }]
|
|
222
|
+
};
|
|
223
|
+
const msg = service.formatResponseAsMessage(requestInfo, response);
|
|
224
|
+
expect(msg).toContain('Question q99');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('formatResponseAsMessage handles no message context', () => {
|
|
228
|
+
const requestInfo = { questions: [{ id: 'q1', message: 'Q' }] };
|
|
229
|
+
const response = { answers: [] };
|
|
230
|
+
const msg = service.formatResponseAsMessage(requestInfo, response);
|
|
231
|
+
expect(msg).not.toContain('Context');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('getPromptService singleton', () => {
|
|
236
|
+
test('returns same instance on multiple calls', () => {
|
|
237
|
+
// Note: the singleton is already created from the module,
|
|
238
|
+
// but we can verify function exists and returns a PromptService
|
|
239
|
+
const s = getPromptService();
|
|
240
|
+
expect(s).toBeInstanceOf(PromptService);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock logger since qualityInspector imports { logger } which doesn't exist as named export
|
|
4
|
+
jest.unstable_mockModule('../../utilities/logger.js', () => ({
|
|
5
|
+
logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() },
|
|
6
|
+
Logger: jest.fn(),
|
|
7
|
+
createLogger: jest.fn()
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const { QualityInspector } = await import('../qualityInspector.js');
|
|
11
|
+
|
|
12
|
+
describe('QualityInspector', () => {
|
|
13
|
+
let inspector;
|
|
14
|
+
let mockOrchestrator;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
mockOrchestrator = {
|
|
18
|
+
sendMessage: jest.fn().mockResolvedValue(undefined),
|
|
19
|
+
sendSystemMessage: jest.fn().mockResolvedValue(undefined),
|
|
20
|
+
pauseAgent: jest.fn().mockResolvedValue(undefined)
|
|
21
|
+
};
|
|
22
|
+
inspector = new QualityInspector(mockOrchestrator);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
inspector.stop();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('constructor creates instance', () => {
|
|
30
|
+
expect(inspector).toBeDefined();
|
|
31
|
+
expect(inspector.isRunning).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('start begins monitoring', () => {
|
|
35
|
+
inspector.start();
|
|
36
|
+
expect(inspector.isRunning).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('stop stops monitoring', () => {
|
|
40
|
+
inspector.start();
|
|
41
|
+
expect(inspector.isRunning).toBe(true);
|
|
42
|
+
inspector.stop();
|
|
43
|
+
expect(inspector.isRunning).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('recordActivity stores activity for agent', () => {
|
|
47
|
+
inspector.start();
|
|
48
|
+
inspector.recordActivity('agent-1', { type: 'command', content: 'ls' });
|
|
49
|
+
|
|
50
|
+
const data = inspector.monitoringData.get('agent-1');
|
|
51
|
+
expect(data).toBeDefined();
|
|
52
|
+
expect(data.activityHistory.length).toBe(1);
|
|
53
|
+
expect(data.messageCount).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('recordActivity does nothing when not running', () => {
|
|
57
|
+
inspector.recordActivity('agent-1', { type: 'command', content: 'ls' });
|
|
58
|
+
expect(inspector.monitoringData.has('agent-1')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('getMetrics returns metrics object', () => {
|
|
62
|
+
const metrics = inspector.getMetrics();
|
|
63
|
+
expect(metrics).toHaveProperty('totalInterventions');
|
|
64
|
+
expect(metrics).toHaveProperty('isRunning');
|
|
65
|
+
expect(typeof metrics.agentsMonitored).toBe('number');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('generateOptimizationSuggestion returns string for slow_response', () => {
|
|
69
|
+
const suggestion = inspector.generateOptimizationSuggestion({ type: 'slow_response' });
|
|
70
|
+
expect(typeof suggestion).toBe('string');
|
|
71
|
+
expect(suggestion.length).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('recordActivity caps history at 100', () => {
|
|
75
|
+
inspector.start();
|
|
76
|
+
for (let i = 0; i < 120; i++) {
|
|
77
|
+
inspector.recordActivity('agent-1', { type: 'command', content: `cmd-${i}` });
|
|
78
|
+
}
|
|
79
|
+
const data = inspector.monitoringData.get('agent-1');
|
|
80
|
+
expect(data.activityHistory.length).toBeLessThanOrEqual(100);
|
|
81
|
+
expect(data.messageCount).toBe(120);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('getAgentReport returns null for unmonitored agent', () => {
|
|
85
|
+
const report = inspector.getAgentReport('nonexistent');
|
|
86
|
+
expect(report).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('getAgentReport returns report for monitored agent', () => {
|
|
90
|
+
inspector.start();
|
|
91
|
+
inspector.recordActivity('agent-1', { type: 'command', content: 'ls' });
|
|
92
|
+
const report = inspector.getAgentReport('agent-1');
|
|
93
|
+
expect(report).not.toBeNull();
|
|
94
|
+
expect(report.agentId).toBe('agent-1');
|
|
95
|
+
expect(report.messageCount).toBe(1);
|
|
96
|
+
});
|
|
97
|
+
});
|