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,204 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger, createMockConfig } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock TagParser before importing HelpTool
|
|
5
|
+
jest.unstable_mockModule('../../utilities/tagParser.js', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
extractContent: jest.fn((content, tag) => {
|
|
8
|
+
const regex = new RegExp(`<${tag}>(.*?)</${tag}>`, 'gs');
|
|
9
|
+
const matches = [];
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = regex.exec(content)) !== null) {
|
|
12
|
+
matches.push(match[1]);
|
|
13
|
+
}
|
|
14
|
+
return matches;
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const { default: HelpTool } = await import('../helpTool.js');
|
|
20
|
+
|
|
21
|
+
describe('HelpTool', () => {
|
|
22
|
+
let tool;
|
|
23
|
+
let logger;
|
|
24
|
+
let config;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
logger = createMockLogger();
|
|
28
|
+
config = createMockConfig();
|
|
29
|
+
tool = new HelpTool(config, logger);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('constructor', () => {
|
|
33
|
+
test('should set correct id and metadata', () => {
|
|
34
|
+
expect(tool.id).toBe('help');
|
|
35
|
+
expect(tool.name).toBe('Help Tool');
|
|
36
|
+
expect(tool.version).toBe('1.0.0');
|
|
37
|
+
expect(tool.requiresProject).toBe(false);
|
|
38
|
+
expect(tool.isAsync).toBe(false);
|
|
39
|
+
expect(tool.toolsRegistry).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getDescription', () => {
|
|
44
|
+
test('should return non-empty description string', () => {
|
|
45
|
+
const desc = tool.getDescription();
|
|
46
|
+
expect(typeof desc).toBe('string');
|
|
47
|
+
expect(desc.length).toBeGreaterThan(0);
|
|
48
|
+
expect(desc).toContain('Help Tool');
|
|
49
|
+
expect(desc).toContain('toolId');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('setToolsRegistry', () => {
|
|
54
|
+
test('should set the registry reference', () => {
|
|
55
|
+
const mockRegistry = { getTool: jest.fn() };
|
|
56
|
+
tool.setToolsRegistry(mockRegistry);
|
|
57
|
+
expect(tool.toolsRegistry).toBe(mockRegistry);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('getSupportedActions', () => {
|
|
62
|
+
test('should return expected actions', () => {
|
|
63
|
+
const actions = tool.getSupportedActions();
|
|
64
|
+
expect(actions).toContain('get-description');
|
|
65
|
+
expect(actions).toContain('list-tools');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('parseParameters', () => {
|
|
70
|
+
test('should parse tool name from tags', () => {
|
|
71
|
+
const result = tool.parseParameters('<tool>filesystem</tool>');
|
|
72
|
+
expect(result.tool).toBe('filesystem');
|
|
73
|
+
expect(result.list).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should parse list=true from tags', () => {
|
|
77
|
+
const result = tool.parseParameters('<list>true</list>');
|
|
78
|
+
expect(result.list).toBe(true);
|
|
79
|
+
expect(result.tool).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should return defaults when no tags found', () => {
|
|
83
|
+
const result = tool.parseParameters('some random content');
|
|
84
|
+
expect(result.tool).toBeNull();
|
|
85
|
+
expect(result.list).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('execute', () => {
|
|
90
|
+
test('should return error when toolsRegistry is not set', async () => {
|
|
91
|
+
const result = await tool.execute({});
|
|
92
|
+
expect(result.success).toBe(false);
|
|
93
|
+
expect(result.error).toContain('not properly initialized');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should return error when no tool specified and list is false', async () => {
|
|
97
|
+
tool.setToolsRegistry({ getTool: jest.fn(), listTools: jest.fn() });
|
|
98
|
+
const result = await tool.execute({ tool: null, list: false });
|
|
99
|
+
expect(result.success).toBe(false);
|
|
100
|
+
expect(result.error).toContain('No tool specified');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should list tools when list=true', async () => {
|
|
104
|
+
const mockRegistry = {
|
|
105
|
+
getTool: jest.fn().mockReturnValue({ isEnabled: true }),
|
|
106
|
+
listTools: jest.fn().mockReturnValue(['filesystem', 'terminal']),
|
|
107
|
+
toolSummaries: new Map([
|
|
108
|
+
['filesystem', 'File operations'],
|
|
109
|
+
['terminal', 'Run commands']
|
|
110
|
+
])
|
|
111
|
+
};
|
|
112
|
+
tool.setToolsRegistry(mockRegistry);
|
|
113
|
+
|
|
114
|
+
const result = await tool.execute({ list: true });
|
|
115
|
+
expect(result.success).toBe(true);
|
|
116
|
+
expect(result.action).toBe('list-tools');
|
|
117
|
+
expect(result.tools).toHaveLength(2);
|
|
118
|
+
expect(result.output).toContain('filesystem');
|
|
119
|
+
expect(result.output).toContain('terminal');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should list tools with disabled indicator', async () => {
|
|
123
|
+
const mockRegistry = {
|
|
124
|
+
getTool: jest.fn().mockReturnValue({ isEnabled: false }),
|
|
125
|
+
listTools: jest.fn().mockReturnValue(['web']),
|
|
126
|
+
toolSummaries: new Map([['web', 'Web browsing']])
|
|
127
|
+
};
|
|
128
|
+
tool.setToolsRegistry(mockRegistry);
|
|
129
|
+
|
|
130
|
+
const result = await tool.execute({ list: true });
|
|
131
|
+
expect(result.output).toContain('(disabled)');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('should get tool description for valid tool', async () => {
|
|
135
|
+
const mockTool = {
|
|
136
|
+
getDescription: jest.fn().mockReturnValue('Full description of filesystem'),
|
|
137
|
+
getCapabilities: jest.fn().mockReturnValue({
|
|
138
|
+
supportedActions: ['read', 'write'],
|
|
139
|
+
async: false,
|
|
140
|
+
requiresProject: true
|
|
141
|
+
})
|
|
142
|
+
};
|
|
143
|
+
const mockRegistry = {
|
|
144
|
+
getTool: jest.fn().mockReturnValue(mockTool),
|
|
145
|
+
listTools: jest.fn().mockReturnValue(['filesystem'])
|
|
146
|
+
};
|
|
147
|
+
tool.setToolsRegistry(mockRegistry);
|
|
148
|
+
|
|
149
|
+
const result = await tool.execute({ tool: 'filesystem' });
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
expect(result.action).toBe('get-description');
|
|
152
|
+
expect(result.toolId).toBe('filesystem');
|
|
153
|
+
expect(result.output).toContain('FILESYSTEM TOOL');
|
|
154
|
+
expect(result.output).toContain('read, write');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should return error for unknown tool', async () => {
|
|
158
|
+
const mockRegistry = {
|
|
159
|
+
getTool: jest.fn().mockReturnValue(null),
|
|
160
|
+
listTools: jest.fn().mockReturnValue(['filesystem', 'terminal'])
|
|
161
|
+
};
|
|
162
|
+
tool.setToolsRegistry(mockRegistry);
|
|
163
|
+
|
|
164
|
+
const result = await tool.execute({ tool: 'nonexistent' });
|
|
165
|
+
expect(result.success).toBe(false);
|
|
166
|
+
expect(result.error).toContain('Tool not found');
|
|
167
|
+
expect(result.output).toContain('nonexistent');
|
|
168
|
+
expect(result.output).toContain('filesystem, terminal');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should handle nested parameters format', async () => {
|
|
172
|
+
const mockTool = {
|
|
173
|
+
getDescription: jest.fn().mockReturnValue('desc'),
|
|
174
|
+
getCapabilities: jest.fn().mockReturnValue({
|
|
175
|
+
supportedActions: ['execute'],
|
|
176
|
+
async: false,
|
|
177
|
+
requiresProject: false
|
|
178
|
+
})
|
|
179
|
+
};
|
|
180
|
+
const mockRegistry = {
|
|
181
|
+
getTool: jest.fn().mockReturnValue(mockTool),
|
|
182
|
+
listTools: jest.fn()
|
|
183
|
+
};
|
|
184
|
+
tool.setToolsRegistry(mockRegistry);
|
|
185
|
+
|
|
186
|
+
const result = await tool.execute({ parameters: { tool: 'terminal' } });
|
|
187
|
+
expect(result.success).toBe(true);
|
|
188
|
+
expect(mockRegistry.getTool).toHaveBeenCalledWith('terminal');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should handle nested list parameter', async () => {
|
|
192
|
+
const mockRegistry = {
|
|
193
|
+
getTool: jest.fn().mockReturnValue({ isEnabled: true }),
|
|
194
|
+
listTools: jest.fn().mockReturnValue([]),
|
|
195
|
+
toolSummaries: new Map()
|
|
196
|
+
};
|
|
197
|
+
tool.setToolsRegistry(mockRegistry);
|
|
198
|
+
|
|
199
|
+
const result = await tool.execute({ parameters: { list: true } });
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
expect(result.action).toBe('list-tools');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
+
import { createMockLogger } from '../../__test-utils__/mockFactories.js';
|
|
3
|
+
|
|
4
|
+
// Mock TagParser before importing
|
|
5
|
+
jest.unstable_mockModule('../../utilities/tagParser.js', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
extractContent: jest.fn((content, tag) => {
|
|
8
|
+
const regex = new RegExp(`<${tag}>(.*?)</${tag}>`, 'gs');
|
|
9
|
+
const matches = [];
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = regex.exec(content)) !== null) {
|
|
12
|
+
matches.push(match[1]);
|
|
13
|
+
}
|
|
14
|
+
return matches;
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const { default: JobDoneTool } = await import('../jobDoneTool.js');
|
|
20
|
+
|
|
21
|
+
describe('JobDoneTool', () => {
|
|
22
|
+
let tool;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
tool = new JobDoneTool();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('constructor', () => {
|
|
29
|
+
test('should set correct id and metadata', () => {
|
|
30
|
+
expect(tool.id).toBe('jobdone');
|
|
31
|
+
expect(tool.name).toBe('Job Done');
|
|
32
|
+
expect(tool.version).toBe('1.0.0');
|
|
33
|
+
expect(tool.requiresProject).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('getDescription', () => {
|
|
38
|
+
test('should return description containing usage info', () => {
|
|
39
|
+
const desc = tool.getDescription();
|
|
40
|
+
expect(desc).toContain('Job Done Tool');
|
|
41
|
+
expect(desc).toContain('complete');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getSchema', () => {
|
|
46
|
+
test('should return valid schema with actions property', () => {
|
|
47
|
+
const schema = tool.getSchema();
|
|
48
|
+
expect(schema.type).toBe('object');
|
|
49
|
+
expect(schema.properties.actions).toBeDefined();
|
|
50
|
+
expect(schema.required).toContain('actions');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('getCapabilities', () => {
|
|
55
|
+
test('should return capabilities with schema', () => {
|
|
56
|
+
const caps = tool.getCapabilities();
|
|
57
|
+
expect(caps.id).toBe('jobdone');
|
|
58
|
+
expect(caps.schema).toBeDefined();
|
|
59
|
+
expect(caps.enabled).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('parseParameters', () => {
|
|
64
|
+
test('should parse structured tags with summary', () => {
|
|
65
|
+
const result = tool.parseParameters('<summary>Task done</summary><success>true</success>');
|
|
66
|
+
expect(result.actions[0].summary).toBe('Task done');
|
|
67
|
+
expect(result.actions[0].success).toBe(true);
|
|
68
|
+
expect(result.actions[0].action).toBe('complete');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should parse with success=false', () => {
|
|
72
|
+
const result = tool.parseParameters('<summary>Failed</summary><success>false</success>');
|
|
73
|
+
expect(result.actions[0].success).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should parse details tag', () => {
|
|
77
|
+
const result = tool.parseParameters('<summary>Done</summary><details>Extra info</details>');
|
|
78
|
+
expect(result.actions[0].details).toBe('Extra info');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should fallback to raw content as summary', () => {
|
|
82
|
+
const result = tool.parseParameters('Simple completion message');
|
|
83
|
+
expect(result.actions[0].summary).toBe('Simple completion message');
|
|
84
|
+
expect(result.actions[0].success).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should default summary to "Task completed" for empty content', () => {
|
|
88
|
+
const result = tool.parseParameters('');
|
|
89
|
+
expect(result.actions[0].summary).toBe('Task completed');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('setAgentPool', () => {
|
|
94
|
+
test('should store the agent pool reference', () => {
|
|
95
|
+
const mockPool = { getAgent: jest.fn() };
|
|
96
|
+
tool.setAgentPool(mockPool);
|
|
97
|
+
expect(tool.agentPool).toBe(mockPool);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('setWebSocketManager', () => {
|
|
102
|
+
test('should store the websocket manager', () => {
|
|
103
|
+
const mockWs = { broadcastToSession: jest.fn() };
|
|
104
|
+
tool.setWebSocketManager(mockWs);
|
|
105
|
+
expect(tool.webSocketManager).toBe(mockWs);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('setFlowExecutor', () => {
|
|
110
|
+
test('should store the flow executor', () => {
|
|
111
|
+
const mockFlow = { notifyAgentCompletion: jest.fn() };
|
|
112
|
+
tool.setFlowExecutor(mockFlow);
|
|
113
|
+
expect(tool.flowExecutor).toBe(mockFlow);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('execute', () => {
|
|
118
|
+
test('should reject when actions array is missing', async () => {
|
|
119
|
+
const result = await tool.execute({}, {});
|
|
120
|
+
expect(result.success).toBe(false);
|
|
121
|
+
expect(result.error).toContain('Actions array is required');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('should reject when actions array is empty', async () => {
|
|
125
|
+
const result = await tool.execute({ actions: [] }, {});
|
|
126
|
+
expect(result.success).toBe(false);
|
|
127
|
+
expect(result.error).toContain('Actions array is required');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should reject invalid action type', async () => {
|
|
131
|
+
const result = await tool.execute({
|
|
132
|
+
actions: [{ action: 'invalid', summary: 'test' }]
|
|
133
|
+
}, {});
|
|
134
|
+
expect(result.success).toBe(false);
|
|
135
|
+
expect(result.error).toContain('Invalid action');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should reject when summary is missing', async () => {
|
|
139
|
+
const result = await tool.execute({
|
|
140
|
+
actions: [{ action: 'complete' }]
|
|
141
|
+
}, {});
|
|
142
|
+
expect(result.success).toBe(false);
|
|
143
|
+
expect(result.error).toContain('summary is required');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should successfully complete with minimal params', async () => {
|
|
147
|
+
const result = await tool.execute({
|
|
148
|
+
actions: [{ action: 'complete', summary: 'All done' }]
|
|
149
|
+
}, {});
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
expect(result.taskComplete).toBe(true);
|
|
152
|
+
expect(result.exitAutonomousMode).toBe(true);
|
|
153
|
+
expect(result.summary).toBe('All done');
|
|
154
|
+
expect(result.successfulCompletion).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should complete with success=false', async () => {
|
|
158
|
+
const result = await tool.execute({
|
|
159
|
+
actions: [{ action: 'complete', summary: 'Stuck', success: false }]
|
|
160
|
+
}, {});
|
|
161
|
+
expect(result.success).toBe(true);
|
|
162
|
+
expect(result.successfulCompletion).toBe(false);
|
|
163
|
+
expect(result.output).toContain('with issues');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should include details in output when provided', async () => {
|
|
167
|
+
const result = await tool.execute({
|
|
168
|
+
actions: [{ action: 'complete', summary: 'Done', details: 'Created 3 files' }]
|
|
169
|
+
}, {});
|
|
170
|
+
expect(result.details).toBe('Created 3 files');
|
|
171
|
+
expect(result.output).toContain('Created 3 files');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should reject when agent has pending tasks and success=true', async () => {
|
|
175
|
+
const mockAgent = {
|
|
176
|
+
name: 'test-agent',
|
|
177
|
+
sessionId: 'sess-1',
|
|
178
|
+
taskList: {
|
|
179
|
+
tasks: [
|
|
180
|
+
{ status: 'pending', title: 'Unfinished task' },
|
|
181
|
+
{ status: 'in_progress', title: 'Working task' }
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const mockPool = {
|
|
186
|
+
getAgent: jest.fn().mockResolvedValue(mockAgent),
|
|
187
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
188
|
+
};
|
|
189
|
+
tool.setAgentPool(mockPool);
|
|
190
|
+
|
|
191
|
+
const result = await tool.execute(
|
|
192
|
+
{ actions: [{ action: 'complete', summary: 'Done', success: true }] },
|
|
193
|
+
{ agentId: 'agent-1' }
|
|
194
|
+
);
|
|
195
|
+
expect(result.success).toBe(false);
|
|
196
|
+
expect(result.output).toContain('pending task(s)');
|
|
197
|
+
expect(result.pendingTasks).toBe(2);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('should allow completion with success=false even with pending tasks', async () => {
|
|
201
|
+
const mockAgent = {
|
|
202
|
+
name: 'test-agent',
|
|
203
|
+
sessionId: 'sess-1',
|
|
204
|
+
taskList: {
|
|
205
|
+
tasks: [{ status: 'pending', title: 'Stuck task' }]
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const mockPool = {
|
|
209
|
+
getAgent: jest.fn().mockResolvedValue(mockAgent),
|
|
210
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
211
|
+
};
|
|
212
|
+
tool.setAgentPool(mockPool);
|
|
213
|
+
|
|
214
|
+
const result = await tool.execute(
|
|
215
|
+
{ actions: [{ action: 'complete', summary: 'Giving up', success: false }] },
|
|
216
|
+
{ agentId: 'agent-1' }
|
|
217
|
+
);
|
|
218
|
+
expect(result.success).toBe(true);
|
|
219
|
+
expect(result.successfulCompletion).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should clear agent tasks and persist state on completion', async () => {
|
|
223
|
+
const mockAgent = {
|
|
224
|
+
id: 'agent-1',
|
|
225
|
+
name: 'test-agent',
|
|
226
|
+
sessionId: 'sess-1',
|
|
227
|
+
taskList: {
|
|
228
|
+
tasks: [{ status: 'completed', title: 'Done task' }]
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const mockPool = {
|
|
232
|
+
getAgent: jest.fn().mockResolvedValue(mockAgent),
|
|
233
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
234
|
+
};
|
|
235
|
+
tool.setAgentPool(mockPool);
|
|
236
|
+
|
|
237
|
+
await tool.execute(
|
|
238
|
+
{ actions: [{ action: 'complete', summary: 'All done' }] },
|
|
239
|
+
{ agentId: 'agent-1' }
|
|
240
|
+
);
|
|
241
|
+
expect(mockAgent.taskList.tasks).toEqual([]);
|
|
242
|
+
expect(mockAgent.autonomousWorkComplete).toBe(true);
|
|
243
|
+
expect(mockPool.persistAgentState).toHaveBeenCalledWith('agent-1');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should broadcast via websocket when available', async () => {
|
|
247
|
+
const mockAgent = {
|
|
248
|
+
id: 'agent-1',
|
|
249
|
+
name: 'test-agent',
|
|
250
|
+
sessionId: 'sess-1',
|
|
251
|
+
mode: 'agent',
|
|
252
|
+
taskList: { tasks: [] }
|
|
253
|
+
};
|
|
254
|
+
const mockPool = {
|
|
255
|
+
getAgent: jest.fn().mockResolvedValue(mockAgent),
|
|
256
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
257
|
+
};
|
|
258
|
+
const mockWs = { broadcastToSession: jest.fn() };
|
|
259
|
+
tool.setAgentPool(mockPool);
|
|
260
|
+
tool.setWebSocketManager(mockWs);
|
|
261
|
+
|
|
262
|
+
await tool.execute(
|
|
263
|
+
{ actions: [{ action: 'complete', summary: 'Done' }] },
|
|
264
|
+
{ agentId: 'agent-1' }
|
|
265
|
+
);
|
|
266
|
+
expect(mockWs.broadcastToSession).toHaveBeenCalledWith('sess-1', expect.objectContaining({
|
|
267
|
+
type: 'agent_job_done'
|
|
268
|
+
}));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('should notify flow executor when set', async () => {
|
|
272
|
+
const mockAgent = {
|
|
273
|
+
id: 'agent-1',
|
|
274
|
+
name: 'test-agent',
|
|
275
|
+
sessionId: 'sess-1',
|
|
276
|
+
taskList: { tasks: [] }
|
|
277
|
+
};
|
|
278
|
+
const mockPool = {
|
|
279
|
+
getAgent: jest.fn().mockResolvedValue(mockAgent),
|
|
280
|
+
persistAgentState: jest.fn().mockResolvedValue(undefined)
|
|
281
|
+
};
|
|
282
|
+
const mockFlow = { notifyAgentCompletion: jest.fn() };
|
|
283
|
+
tool.setAgentPool(mockPool);
|
|
284
|
+
tool.setFlowExecutor(mockFlow);
|
|
285
|
+
|
|
286
|
+
await tool.execute(
|
|
287
|
+
{ actions: [{ action: 'complete', summary: 'Done' }] },
|
|
288
|
+
{ agentId: 'agent-1' }
|
|
289
|
+
);
|
|
290
|
+
expect(mockFlow.notifyAgentCompletion).toHaveBeenCalledWith('agent-1', expect.objectContaining({
|
|
291
|
+
summary: 'Done',
|
|
292
|
+
success: true
|
|
293
|
+
}));
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|