@sparkleideas/shared 3.0.0-alpha.7
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/README.md +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Session Hooks Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for session-end and session-restore hook functionality.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/shared/hooks/__tests__/session-hooks.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
createHookRegistry,
|
|
12
|
+
createSessionHooksManager,
|
|
13
|
+
SessionHooksManager,
|
|
14
|
+
HookRegistry,
|
|
15
|
+
HookEvent,
|
|
16
|
+
InMemorySessionStorage,
|
|
17
|
+
} from '../../src/hooks/index.js';
|
|
18
|
+
|
|
19
|
+
describe('SessionHooksManager', () => {
|
|
20
|
+
let registry: HookRegistry;
|
|
21
|
+
let sessionManager: SessionHooksManager;
|
|
22
|
+
let storage: InMemorySessionStorage;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
registry = createHookRegistry();
|
|
26
|
+
storage = new InMemorySessionStorage();
|
|
27
|
+
sessionManager = createSessionHooksManager(registry, storage);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('session lifecycle hooks', () => {
|
|
31
|
+
it('should register session hooks on creation', () => {
|
|
32
|
+
const startHooks = registry.getHandlers(HookEvent.SessionStart);
|
|
33
|
+
const endHooks = registry.getHandlers(HookEvent.SessionEnd);
|
|
34
|
+
const resumeHooks = registry.getHandlers(HookEvent.SessionResume);
|
|
35
|
+
|
|
36
|
+
expect(startHooks.some(h => h.name === 'session-hooks:start')).toBe(true);
|
|
37
|
+
expect(endHooks.some(h => h.name === 'session-hooks:end')).toBe(true);
|
|
38
|
+
expect(resumeHooks.some(h => h.name === 'session-hooks:resume')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('session-end hook', () => {
|
|
43
|
+
it('should end session and return summary', async () => {
|
|
44
|
+
// Simulate session start by triggering tracking
|
|
45
|
+
const context = {
|
|
46
|
+
event: HookEvent.SessionStart,
|
|
47
|
+
timestamp: new Date(),
|
|
48
|
+
session: { id: 'test-session', startTime: new Date() },
|
|
49
|
+
};
|
|
50
|
+
await sessionManager['handleSessionStart'](context);
|
|
51
|
+
|
|
52
|
+
// Wait a moment to ensure duration > 0
|
|
53
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
54
|
+
|
|
55
|
+
const result = await sessionManager.executeSessionEnd();
|
|
56
|
+
|
|
57
|
+
expect(result.success).toBe(true);
|
|
58
|
+
expect(result.duration).toBeGreaterThanOrEqual(0);
|
|
59
|
+
expect(result.summary).toBeDefined();
|
|
60
|
+
expect(result.summary!.tasksExecuted).toBe(0);
|
|
61
|
+
expect(result.summary!.commandsExecuted).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should persist session state', async () => {
|
|
65
|
+
// Start session
|
|
66
|
+
const startContext = {
|
|
67
|
+
event: HookEvent.SessionStart,
|
|
68
|
+
timestamp: new Date(),
|
|
69
|
+
session: { id: 'persist-session', startTime: new Date() },
|
|
70
|
+
};
|
|
71
|
+
await sessionManager['handleSessionStart'](startContext);
|
|
72
|
+
|
|
73
|
+
const result = await sessionManager.executeSessionEnd();
|
|
74
|
+
|
|
75
|
+
expect(result.persistedState).toBeDefined();
|
|
76
|
+
expect(result.statePath).toBeDefined();
|
|
77
|
+
|
|
78
|
+
// Verify state was saved
|
|
79
|
+
const sessions = await storage.list();
|
|
80
|
+
expect(sessions.length).toBeGreaterThan(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle ending session without active session', async () => {
|
|
84
|
+
const result = await sessionManager.executeSessionEnd();
|
|
85
|
+
expect(result.success).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should reset activity tracking after session end', async () => {
|
|
89
|
+
// Start session
|
|
90
|
+
const context = {
|
|
91
|
+
event: HookEvent.SessionStart,
|
|
92
|
+
timestamp: new Date(),
|
|
93
|
+
session: { id: 'reset-session', startTime: new Date() },
|
|
94
|
+
};
|
|
95
|
+
await sessionManager['handleSessionStart'](context);
|
|
96
|
+
|
|
97
|
+
await sessionManager.executeSessionEnd();
|
|
98
|
+
|
|
99
|
+
expect(sessionManager.getCurrentSessionId()).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('session-restore hook', () => {
|
|
104
|
+
it('should restore a previous session', async () => {
|
|
105
|
+
// Create and end a session first
|
|
106
|
+
const startContext = {
|
|
107
|
+
event: HookEvent.SessionStart,
|
|
108
|
+
timestamp: new Date(),
|
|
109
|
+
session: { id: 'restore-test', startTime: new Date() },
|
|
110
|
+
};
|
|
111
|
+
await sessionManager['handleSessionStart'](startContext);
|
|
112
|
+
await sessionManager.executeSessionEnd();
|
|
113
|
+
|
|
114
|
+
// Restore the session
|
|
115
|
+
const result = await sessionManager.executeSessionRestore('restore-test');
|
|
116
|
+
|
|
117
|
+
expect(result.success).toBe(true);
|
|
118
|
+
expect(result.restoredState).toBeDefined();
|
|
119
|
+
expect(result.restoredState!.sessionId).toBe('restore-test');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should restore latest session when no ID specified', async () => {
|
|
123
|
+
// Create and end multiple sessions
|
|
124
|
+
for (const id of ['session-1', 'session-2', 'session-3']) {
|
|
125
|
+
const context = {
|
|
126
|
+
event: HookEvent.SessionStart,
|
|
127
|
+
timestamp: new Date(),
|
|
128
|
+
session: { id, startTime: new Date() },
|
|
129
|
+
};
|
|
130
|
+
await sessionManager['handleSessionStart'](context);
|
|
131
|
+
await sessionManager.executeSessionEnd();
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await sessionManager.executeSessionRestore();
|
|
136
|
+
|
|
137
|
+
expect(result.success).toBe(true);
|
|
138
|
+
expect(result.restoredState).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should fail gracefully when session not found', async () => {
|
|
142
|
+
const result = await sessionManager.executeSessionRestore('non-existent');
|
|
143
|
+
|
|
144
|
+
expect(result.success).toBe(false);
|
|
145
|
+
expect(result.error).toBeDefined();
|
|
146
|
+
expect(result.warnings).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should return warnings for old sessions', async () => {
|
|
150
|
+
// Create a session with an old timestamp
|
|
151
|
+
const oldSession = {
|
|
152
|
+
sessionId: 'old-session',
|
|
153
|
+
startTime: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000), // 8 days ago
|
|
154
|
+
endTime: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000),
|
|
155
|
+
};
|
|
156
|
+
await storage.save('old-session', oldSession);
|
|
157
|
+
|
|
158
|
+
const result = await sessionManager.executeSessionRestore('old-session');
|
|
159
|
+
|
|
160
|
+
expect(result.success).toBe(true);
|
|
161
|
+
expect(result.warnings).toBeDefined();
|
|
162
|
+
expect(result.warnings!.some(w => w.includes('days old'))).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should start a new session after restoration', async () => {
|
|
166
|
+
// Create and end a session
|
|
167
|
+
const context = {
|
|
168
|
+
event: HookEvent.SessionStart,
|
|
169
|
+
timestamp: new Date(),
|
|
170
|
+
session: { id: 'new-after-restore', startTime: new Date() },
|
|
171
|
+
};
|
|
172
|
+
await sessionManager['handleSessionStart'](context);
|
|
173
|
+
await sessionManager.executeSessionEnd();
|
|
174
|
+
|
|
175
|
+
await sessionManager.executeSessionRestore('new-after-restore');
|
|
176
|
+
|
|
177
|
+
expect(sessionManager.getCurrentSessionId()).toBeDefined();
|
|
178
|
+
expect(sessionManager.getCurrentSessionId()).toContain('restored');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should count restored items', async () => {
|
|
182
|
+
// Create a session with tasks and agents
|
|
183
|
+
const sessionState = {
|
|
184
|
+
sessionId: 'count-test',
|
|
185
|
+
startTime: new Date(),
|
|
186
|
+
endTime: new Date(),
|
|
187
|
+
activeTasks: [
|
|
188
|
+
{ id: 'task-1', description: 'Task 1', status: 'completed' as const },
|
|
189
|
+
{ id: 'task-2', description: 'Task 2', status: 'in_progress' as const },
|
|
190
|
+
],
|
|
191
|
+
spawnedAgents: [
|
|
192
|
+
{ id: 'agent-1', type: 'coder', status: 'active' as const },
|
|
193
|
+
],
|
|
194
|
+
memoryEntries: [
|
|
195
|
+
{ key: 'key-1', namespace: 'default', type: 'string' },
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
await storage.save('count-test', sessionState);
|
|
199
|
+
|
|
200
|
+
const result = await sessionManager.executeSessionRestore('count-test');
|
|
201
|
+
|
|
202
|
+
expect(result.tasksRestored).toBe(2);
|
|
203
|
+
expect(result.agentsRestored).toBe(1);
|
|
204
|
+
expect(result.memoryRestored).toBe(1);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('activity tracking', () => {
|
|
209
|
+
it('should track task executions', async () => {
|
|
210
|
+
// Start session
|
|
211
|
+
const context = {
|
|
212
|
+
event: HookEvent.SessionStart,
|
|
213
|
+
timestamp: new Date(),
|
|
214
|
+
session: { id: 'track-tasks', startTime: new Date() },
|
|
215
|
+
};
|
|
216
|
+
await sessionManager['handleSessionStart'](context);
|
|
217
|
+
|
|
218
|
+
// Track a successful task
|
|
219
|
+
await sessionManager['trackTaskExecution']({
|
|
220
|
+
event: HookEvent.PostTaskExecute,
|
|
221
|
+
timestamp: new Date(),
|
|
222
|
+
metadata: { success: true },
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Track a failed task
|
|
226
|
+
await sessionManager['trackTaskExecution']({
|
|
227
|
+
event: HookEvent.PostTaskExecute,
|
|
228
|
+
timestamp: new Date(),
|
|
229
|
+
metadata: { success: false },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const activity = sessionManager.getCurrentActivity();
|
|
233
|
+
expect(activity.tasksExecuted).toBe(2);
|
|
234
|
+
expect(activity.tasksSucceeded).toBe(1);
|
|
235
|
+
expect(activity.tasksFailed).toBe(1);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should track command executions', async () => {
|
|
239
|
+
// Start session
|
|
240
|
+
const context = {
|
|
241
|
+
event: HookEvent.SessionStart,
|
|
242
|
+
timestamp: new Date(),
|
|
243
|
+
session: { id: 'track-commands', startTime: new Date() },
|
|
244
|
+
};
|
|
245
|
+
await sessionManager['handleSessionStart'](context);
|
|
246
|
+
|
|
247
|
+
// Track commands
|
|
248
|
+
await sessionManager['trackCommandExecution']({
|
|
249
|
+
event: HookEvent.PostCommand,
|
|
250
|
+
timestamp: new Date(),
|
|
251
|
+
});
|
|
252
|
+
await sessionManager['trackCommandExecution']({
|
|
253
|
+
event: HookEvent.PostCommand,
|
|
254
|
+
timestamp: new Date(),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const activity = sessionManager.getCurrentActivity();
|
|
258
|
+
expect(activity.commandsExecuted).toBe(2);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should track file modifications', async () => {
|
|
262
|
+
// Start session
|
|
263
|
+
const context = {
|
|
264
|
+
event: HookEvent.SessionStart,
|
|
265
|
+
timestamp: new Date(),
|
|
266
|
+
session: { id: 'track-files', startTime: new Date() },
|
|
267
|
+
};
|
|
268
|
+
await sessionManager['handleSessionStart'](context);
|
|
269
|
+
|
|
270
|
+
// Track file modifications
|
|
271
|
+
await sessionManager['trackFileModification']({
|
|
272
|
+
event: HookEvent.PostEdit,
|
|
273
|
+
timestamp: new Date(),
|
|
274
|
+
file: { path: '/src/file1.ts', operation: 'edit' },
|
|
275
|
+
});
|
|
276
|
+
await sessionManager['trackFileModification']({
|
|
277
|
+
event: HookEvent.PostEdit,
|
|
278
|
+
timestamp: new Date(),
|
|
279
|
+
file: { path: '/src/file2.ts', operation: 'edit' },
|
|
280
|
+
});
|
|
281
|
+
// Same file again
|
|
282
|
+
await sessionManager['trackFileModification']({
|
|
283
|
+
event: HookEvent.PostEdit,
|
|
284
|
+
timestamp: new Date(),
|
|
285
|
+
file: { path: '/src/file1.ts', operation: 'edit' },
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const activity = sessionManager.getCurrentActivity();
|
|
289
|
+
expect(activity.filesModified.size).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should track agent spawns', async () => {
|
|
293
|
+
// Start session
|
|
294
|
+
const context = {
|
|
295
|
+
event: HookEvent.SessionStart,
|
|
296
|
+
timestamp: new Date(),
|
|
297
|
+
session: { id: 'track-agents', startTime: new Date() },
|
|
298
|
+
};
|
|
299
|
+
await sessionManager['handleSessionStart'](context);
|
|
300
|
+
|
|
301
|
+
// Track agent spawns
|
|
302
|
+
await sessionManager['trackAgentSpawn']({
|
|
303
|
+
event: HookEvent.PostAgentSpawn,
|
|
304
|
+
timestamp: new Date(),
|
|
305
|
+
agent: { id: 'agent-1', type: 'coder' },
|
|
306
|
+
});
|
|
307
|
+
await sessionManager['trackAgentSpawn']({
|
|
308
|
+
event: HookEvent.PostAgentSpawn,
|
|
309
|
+
timestamp: new Date(),
|
|
310
|
+
agent: { id: 'agent-2', type: 'tester' },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const activity = sessionManager.getCurrentActivity();
|
|
314
|
+
expect(activity.agentsSpawned.size).toBe(2);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('session management', () => {
|
|
319
|
+
it('should list available sessions', async () => {
|
|
320
|
+
// Create multiple sessions
|
|
321
|
+
for (const id of ['list-1', 'list-2', 'list-3']) {
|
|
322
|
+
const context = {
|
|
323
|
+
event: HookEvent.SessionStart,
|
|
324
|
+
timestamp: new Date(),
|
|
325
|
+
session: { id, startTime: new Date() },
|
|
326
|
+
};
|
|
327
|
+
await sessionManager['handleSessionStart'](context);
|
|
328
|
+
await sessionManager.executeSessionEnd();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const sessions = await sessionManager.listSessions();
|
|
332
|
+
expect(sessions.length).toBe(3);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should delete a session', async () => {
|
|
336
|
+
// Create a session
|
|
337
|
+
const context = {
|
|
338
|
+
event: HookEvent.SessionStart,
|
|
339
|
+
timestamp: new Date(),
|
|
340
|
+
session: { id: 'delete-me', startTime: new Date() },
|
|
341
|
+
};
|
|
342
|
+
await sessionManager['handleSessionStart'](context);
|
|
343
|
+
await sessionManager.executeSessionEnd();
|
|
344
|
+
|
|
345
|
+
const deleted = await sessionManager.deleteSession('delete-me');
|
|
346
|
+
expect(deleted).toBe(true);
|
|
347
|
+
|
|
348
|
+
const sessions = await sessionManager.listSessions();
|
|
349
|
+
expect(sessions.find(s => s.id === 'delete-me')).toBeUndefined();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should return false when deleting non-existent session', async () => {
|
|
353
|
+
const deleted = await sessionManager.deleteSession('non-existent');
|
|
354
|
+
expect(deleted).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Task Hooks Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for pre-task and post-task hook functionality.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/shared/hooks/__tests__/task-hooks.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
createHookRegistry,
|
|
12
|
+
createTaskHooksManager,
|
|
13
|
+
TaskHooksManager,
|
|
14
|
+
HookRegistry,
|
|
15
|
+
HookEvent,
|
|
16
|
+
} from '../../src/hooks/index.js';
|
|
17
|
+
|
|
18
|
+
describe('TaskHooksManager', () => {
|
|
19
|
+
let registry: HookRegistry;
|
|
20
|
+
let taskManager: TaskHooksManager;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
registry = createHookRegistry();
|
|
24
|
+
taskManager = createTaskHooksManager(registry);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('pre-task hook', () => {
|
|
28
|
+
it('should register pre-task hook on creation', () => {
|
|
29
|
+
const hooks = registry.getHandlers(HookEvent.PreTaskExecute);
|
|
30
|
+
expect(hooks.length).toBeGreaterThan(0);
|
|
31
|
+
expect(hooks.some(h => h.name === 'task-hooks:pre-task')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should analyze task and suggest agents for coding task', async () => {
|
|
35
|
+
const result = await taskManager.executePreTask(
|
|
36
|
+
'task-123',
|
|
37
|
+
'Implement user authentication feature'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(result.success).toBe(true);
|
|
41
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
42
|
+
expect(result.suggestedAgents!.length).toBeGreaterThan(0);
|
|
43
|
+
expect(result.suggestedAgents![0].type).toBe('coder');
|
|
44
|
+
expect(result.suggestedAgents![0].confidence).toBeGreaterThan(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should suggest security-architect for security tasks', async () => {
|
|
48
|
+
const result = await taskManager.executePreTask(
|
|
49
|
+
'task-456',
|
|
50
|
+
'Fix security vulnerability in authentication'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
55
|
+
const securityAgent = result.suggestedAgents!.find(a => a.type === 'security-architect');
|
|
56
|
+
expect(securityAgent).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should suggest tester for test-related tasks', async () => {
|
|
60
|
+
const result = await taskManager.executePreTask(
|
|
61
|
+
'task-789',
|
|
62
|
+
'Write unit tests for user service'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(result.success).toBe(true);
|
|
66
|
+
expect(result.suggestedAgents).toBeDefined();
|
|
67
|
+
const testerAgent = result.suggestedAgents!.find(a => a.type === 'tester');
|
|
68
|
+
expect(testerAgent).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should estimate complexity based on task description', async () => {
|
|
72
|
+
// Simple task
|
|
73
|
+
const simpleResult = await taskManager.executePreTask(
|
|
74
|
+
'task-simple',
|
|
75
|
+
'Fix typo in readme'
|
|
76
|
+
);
|
|
77
|
+
expect(simpleResult.complexity).toBe('low');
|
|
78
|
+
|
|
79
|
+
// Complex task
|
|
80
|
+
const complexResult = await taskManager.executePreTask(
|
|
81
|
+
'task-complex',
|
|
82
|
+
'Refactor and redesign the entire authentication system with multiple OAuth providers'
|
|
83
|
+
);
|
|
84
|
+
expect(complexResult.complexity).toBe('high');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should detect risks in task description', async () => {
|
|
88
|
+
const result = await taskManager.executePreTask(
|
|
89
|
+
'task-risky',
|
|
90
|
+
'Delete old data from production database'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(result.risks).toBeDefined();
|
|
94
|
+
expect(result.risks!.length).toBeGreaterThan(0);
|
|
95
|
+
expect(result.risks!.some(r => r.includes('production'))).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should track active tasks', async () => {
|
|
99
|
+
await taskManager.executePreTask('task-1', 'Task 1');
|
|
100
|
+
await taskManager.executePreTask('task-2', 'Task 2');
|
|
101
|
+
|
|
102
|
+
const activeTasks = taskManager.getActiveTasks();
|
|
103
|
+
expect(activeTasks.size).toBe(2);
|
|
104
|
+
expect(activeTasks.has('task-1')).toBe(true);
|
|
105
|
+
expect(activeTasks.has('task-2')).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should provide recommendations for high complexity tasks', async () => {
|
|
109
|
+
const result = await taskManager.executePreTask(
|
|
110
|
+
'task-high-complexity',
|
|
111
|
+
'Implement complex distributed system with multiple services'
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(result.recommendations).toBeDefined();
|
|
115
|
+
expect(result.recommendations!.length).toBeGreaterThan(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('post-task hook', () => {
|
|
120
|
+
it('should register post-task hook on creation', () => {
|
|
121
|
+
const hooks = registry.getHandlers(HookEvent.PostTaskExecute);
|
|
122
|
+
expect(hooks.length).toBeGreaterThan(0);
|
|
123
|
+
expect(hooks.some(h => h.name === 'task-hooks:post-task')).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should record successful task outcome', async () => {
|
|
127
|
+
// First start the task
|
|
128
|
+
await taskManager.executePreTask('task-success', 'Test task');
|
|
129
|
+
|
|
130
|
+
// Then complete it
|
|
131
|
+
const result = await taskManager.executePostTask('task-success', true);
|
|
132
|
+
|
|
133
|
+
expect(result.success).toBe(true);
|
|
134
|
+
expect(result.outcome).toBeDefined();
|
|
135
|
+
expect(result.outcome!.success).toBe(true);
|
|
136
|
+
expect(result.outcome!.duration).toBeGreaterThanOrEqual(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should record failed task outcome', async () => {
|
|
140
|
+
await taskManager.executePreTask('task-failed', 'Test task');
|
|
141
|
+
|
|
142
|
+
const result = await taskManager.executePostTask('task-failed', false, {
|
|
143
|
+
error: 'Test failed due to timeout',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(result.success).toBe(true);
|
|
147
|
+
expect(result.outcome).toBeDefined();
|
|
148
|
+
expect(result.outcome!.success).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should create learning trajectory', async () => {
|
|
152
|
+
await taskManager.executePreTask('task-learn', 'Test task');
|
|
153
|
+
const result = await taskManager.executePostTask('task-learn', true);
|
|
154
|
+
|
|
155
|
+
expect(result.trajectoryId).toBeDefined();
|
|
156
|
+
expect(result.trajectoryId).toContain('trajectory-');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should track learning updates', async () => {
|
|
160
|
+
await taskManager.executePreTask('task-updates', 'Test task');
|
|
161
|
+
const result = await taskManager.executePostTask('task-updates', true);
|
|
162
|
+
|
|
163
|
+
expect(result.learningUpdates).toBeDefined();
|
|
164
|
+
expect(result.learningUpdates!.trajectoriesRecorded).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should remove task from active tasks after completion', async () => {
|
|
168
|
+
await taskManager.executePreTask('task-cleanup', 'Test task');
|
|
169
|
+
expect(taskManager.getActiveTasks().has('task-cleanup')).toBe(true);
|
|
170
|
+
|
|
171
|
+
await taskManager.executePostTask('task-cleanup', true);
|
|
172
|
+
expect(taskManager.getActiveTasks().has('task-cleanup')).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle post-task without pre-task gracefully', async () => {
|
|
176
|
+
const result = await taskManager.executePostTask('task-no-pre', true);
|
|
177
|
+
|
|
178
|
+
expect(result.success).toBe(true);
|
|
179
|
+
expect(result.outcome).toBeDefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('clearActiveTasks', () => {
|
|
184
|
+
it('should clear all active tasks', async () => {
|
|
185
|
+
await taskManager.executePreTask('task-1', 'Task 1');
|
|
186
|
+
await taskManager.executePreTask('task-2', 'Task 2');
|
|
187
|
+
expect(taskManager.getActiveTasks().size).toBe(2);
|
|
188
|
+
|
|
189
|
+
taskManager.clearActiveTasks();
|
|
190
|
+
expect(taskManager.getActiveTasks().size).toBe(0);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|