@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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Session Manager
|
|
3
|
+
* Decomposed from orchestrator.ts - Session handling
|
|
4
|
+
* ~200 lines (target achieved)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IAgentSession } from '../interfaces/agent.interface.js';
|
|
8
|
+
import type { IEventBus } from '../interfaces/event.interface.js';
|
|
9
|
+
import { SystemEventTypes } from '../interfaces/event.interface.js';
|
|
10
|
+
import type { AgentProfile } from '../../types/agent.types.js';
|
|
11
|
+
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
12
|
+
import { join, dirname } from 'path';
|
|
13
|
+
import { randomBytes } from 'crypto';
|
|
14
|
+
|
|
15
|
+
// Secure session ID generation
|
|
16
|
+
function generateSecureSessionId(): string {
|
|
17
|
+
const timestamp = Date.now().toString(36);
|
|
18
|
+
const random = randomBytes(12).toString('hex');
|
|
19
|
+
return `session_${timestamp}_${random}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Session persistence structure
|
|
24
|
+
*/
|
|
25
|
+
export interface SessionPersistence {
|
|
26
|
+
sessions: Array<IAgentSession & { profile: AgentProfile }>;
|
|
27
|
+
metrics: {
|
|
28
|
+
completedTasks: number;
|
|
29
|
+
failedTasks: number;
|
|
30
|
+
totalTaskDuration: number;
|
|
31
|
+
};
|
|
32
|
+
savedAt: Date;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Session manager configuration
|
|
37
|
+
*/
|
|
38
|
+
export interface SessionManagerConfig {
|
|
39
|
+
persistSessions: boolean;
|
|
40
|
+
dataDir: string;
|
|
41
|
+
sessionRetentionMs?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Session manager interface
|
|
46
|
+
*/
|
|
47
|
+
export interface ISessionManager {
|
|
48
|
+
createSession(profile: AgentProfile, terminalId: string, memoryBankId: string): Promise<IAgentSession>;
|
|
49
|
+
getSession(sessionId: string): IAgentSession | undefined;
|
|
50
|
+
getActiveSessions(): IAgentSession[];
|
|
51
|
+
getSessionsByAgent(agentId: string): IAgentSession[];
|
|
52
|
+
terminateSession(sessionId: string): Promise<void>;
|
|
53
|
+
terminateAllSessions(): Promise<void>;
|
|
54
|
+
persistSessions(): Promise<void>;
|
|
55
|
+
restoreSessions(): Promise<SessionPersistence | null>;
|
|
56
|
+
removeSession(sessionId: string): void;
|
|
57
|
+
updateSessionActivity(sessionId: string): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Session manager implementation
|
|
62
|
+
*/
|
|
63
|
+
export class SessionManager implements ISessionManager {
|
|
64
|
+
private sessions = new Map<string, IAgentSession>();
|
|
65
|
+
private sessionProfiles = new Map<string, AgentProfile>();
|
|
66
|
+
private persistencePath: string;
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
private eventBus: IEventBus,
|
|
70
|
+
private config: SessionManagerConfig,
|
|
71
|
+
) {
|
|
72
|
+
this.persistencePath = join(config.dataDir || './data', 'sessions.json');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async createSession(
|
|
76
|
+
profile: AgentProfile,
|
|
77
|
+
terminalId: string,
|
|
78
|
+
memoryBankId: string,
|
|
79
|
+
): Promise<IAgentSession> {
|
|
80
|
+
const session: IAgentSession = {
|
|
81
|
+
id: generateSecureSessionId(),
|
|
82
|
+
agentId: profile.id,
|
|
83
|
+
terminalId,
|
|
84
|
+
startTime: new Date(),
|
|
85
|
+
status: 'active',
|
|
86
|
+
lastActivity: new Date(),
|
|
87
|
+
memoryBankId,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this.sessions.set(session.id, session);
|
|
91
|
+
this.sessionProfiles.set(session.id, profile);
|
|
92
|
+
|
|
93
|
+
this.eventBus.emit(SystemEventTypes.SESSION_CREATED, {
|
|
94
|
+
sessionId: session.id,
|
|
95
|
+
agentId: profile.id,
|
|
96
|
+
terminalId,
|
|
97
|
+
memoryBankId,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Persist sessions asynchronously
|
|
101
|
+
this.persistSessions().catch(() => {
|
|
102
|
+
// Silently ignore persistence errors
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return session;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getSession(sessionId: string): IAgentSession | undefined {
|
|
109
|
+
return this.sessions.get(sessionId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getActiveSessions(): IAgentSession[] {
|
|
113
|
+
return Array.from(this.sessions.values()).filter(
|
|
114
|
+
session => session.status === 'active' || session.status === 'idle',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getSessionsByAgent(agentId: string): IAgentSession[] {
|
|
119
|
+
return Array.from(this.sessions.values()).filter(
|
|
120
|
+
session => session.agentId === agentId,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async terminateSession(sessionId: string): Promise<void> {
|
|
125
|
+
const session = this.sessions.get(sessionId);
|
|
126
|
+
if (!session) {
|
|
127
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
session.status = 'terminated';
|
|
131
|
+
session.endTime = new Date();
|
|
132
|
+
|
|
133
|
+
const duration = session.endTime.getTime() - session.startTime.getTime();
|
|
134
|
+
|
|
135
|
+
this.eventBus.emit(SystemEventTypes.SESSION_TERMINATED, {
|
|
136
|
+
sessionId,
|
|
137
|
+
agentId: session.agentId,
|
|
138
|
+
duration,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Clean up profile reference
|
|
142
|
+
this.sessionProfiles.delete(sessionId);
|
|
143
|
+
|
|
144
|
+
// Persist sessions asynchronously
|
|
145
|
+
this.persistSessions().catch(() => {
|
|
146
|
+
// Silently ignore persistence errors
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async terminateAllSessions(): Promise<void> {
|
|
151
|
+
const sessions = this.getActiveSessions();
|
|
152
|
+
const batchSize = 5;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < sessions.length; i += batchSize) {
|
|
155
|
+
const batch = sessions.slice(i, i + batchSize);
|
|
156
|
+
await Promise.allSettled(
|
|
157
|
+
batch.map(session => this.terminateSession(session.id)),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
removeSession(sessionId: string): void {
|
|
163
|
+
this.sessions.delete(sessionId);
|
|
164
|
+
this.sessionProfiles.delete(sessionId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
updateSessionActivity(sessionId: string): void {
|
|
168
|
+
const session = this.sessions.get(sessionId);
|
|
169
|
+
if (session) {
|
|
170
|
+
session.lastActivity = new Date();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async persistSessions(): Promise<void> {
|
|
175
|
+
if (!this.config.persistSessions) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const data: SessionPersistence = {
|
|
181
|
+
sessions: Array.from(this.sessions.values())
|
|
182
|
+
.map(session => ({
|
|
183
|
+
...session,
|
|
184
|
+
profile: this.sessionProfiles.get(session.id)!,
|
|
185
|
+
}))
|
|
186
|
+
.filter(s => s.profile),
|
|
187
|
+
metrics: {
|
|
188
|
+
completedTasks: 0,
|
|
189
|
+
failedTasks: 0,
|
|
190
|
+
totalTaskDuration: 0,
|
|
191
|
+
},
|
|
192
|
+
savedAt: new Date(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await mkdir(dirname(this.persistencePath), { recursive: true });
|
|
196
|
+
await writeFile(this.persistencePath, JSON.stringify(data, null, 2), 'utf8');
|
|
197
|
+
|
|
198
|
+
this.eventBus.emit(SystemEventTypes.SESSION_PERSISTED, {
|
|
199
|
+
sessionCount: data.sessions.length,
|
|
200
|
+
path: this.persistencePath,
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Let caller handle persistence errors
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async restoreSessions(): Promise<SessionPersistence | null> {
|
|
209
|
+
if (!this.config.persistSessions) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const data = await readFile(this.persistencePath, 'utf8');
|
|
215
|
+
const persistence: SessionPersistence = JSON.parse(data);
|
|
216
|
+
|
|
217
|
+
// Filter to only active/idle sessions
|
|
218
|
+
const sessionsToRestore = persistence.sessions.filter(
|
|
219
|
+
s => s.status === 'active' || s.status === 'idle',
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
this.eventBus.emit(SystemEventTypes.SESSION_RESTORED, {
|
|
223
|
+
sessionCount: sessionsToRestore.length,
|
|
224
|
+
path: this.persistencePath,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
...persistence,
|
|
229
|
+
sessions: sessionsToRestore,
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Clean up old terminated sessions
|
|
241
|
+
*/
|
|
242
|
+
async cleanupTerminatedSessions(retentionMs?: number): Promise<number> {
|
|
243
|
+
const cutoffTime = Date.now() - (retentionMs ?? this.config.sessionRetentionMs ?? 3600000);
|
|
244
|
+
let cleaned = 0;
|
|
245
|
+
|
|
246
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
247
|
+
if (session.status === 'terminated' && session.endTime) {
|
|
248
|
+
if (session.endTime.getTime() < cutoffTime) {
|
|
249
|
+
this.sessions.delete(sessionId);
|
|
250
|
+
this.sessionProfiles.delete(sessionId);
|
|
251
|
+
cleaned++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return cleaned;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get session profile
|
|
261
|
+
*/
|
|
262
|
+
getSessionProfile(sessionId: string): AgentProfile | undefined {
|
|
263
|
+
return this.sessionProfiles.get(sessionId);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get session count
|
|
268
|
+
*/
|
|
269
|
+
getSessionCount(): number {
|
|
270
|
+
return this.sessions.size;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get active session count
|
|
275
|
+
*/
|
|
276
|
+
getActiveSessionCount(): number {
|
|
277
|
+
return this.getActiveSessions().length;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Task Manager
|
|
3
|
+
* Decomposed from orchestrator.ts - Task lifecycle management
|
|
4
|
+
* ~200 lines (target achieved)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ITask,
|
|
9
|
+
ITaskCreate,
|
|
10
|
+
ITaskResult,
|
|
11
|
+
ITaskManager,
|
|
12
|
+
ITaskQueue,
|
|
13
|
+
TaskManagerMetrics,
|
|
14
|
+
TaskStatus,
|
|
15
|
+
} from '../interfaces/task.interface.js';
|
|
16
|
+
import type { IEventBus, SystemEventType } from '../interfaces/event.interface.js';
|
|
17
|
+
import { SystemEventTypes } from '../interfaces/event.interface.js';
|
|
18
|
+
import { randomBytes } from 'crypto';
|
|
19
|
+
|
|
20
|
+
// Secure task ID generation
|
|
21
|
+
function generateSecureTaskId(): string {
|
|
22
|
+
const timestamp = Date.now().toString(36);
|
|
23
|
+
const random = randomBytes(12).toString('hex');
|
|
24
|
+
return `task_${timestamp}_${random}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Priority queue implementation for tasks
|
|
29
|
+
*/
|
|
30
|
+
export class TaskQueue implements ITaskQueue {
|
|
31
|
+
private tasks: ITask[] = [];
|
|
32
|
+
|
|
33
|
+
async enqueue(task: ITask): Promise<void> {
|
|
34
|
+
this.tasks.push(task);
|
|
35
|
+
this.tasks.sort((a, b) => b.priority - a.priority);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async dequeue(): Promise<ITask | undefined> {
|
|
39
|
+
return this.tasks.shift();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async peek(): Promise<ITask | undefined> {
|
|
43
|
+
return this.tasks[0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
size(): number {
|
|
47
|
+
return this.tasks.length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isEmpty(): boolean {
|
|
51
|
+
return this.tasks.length === 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async clear(): Promise<void> {
|
|
55
|
+
this.tasks = [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getAll(): Promise<ITask[]> {
|
|
59
|
+
return [...this.tasks];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async remove(taskId: string): Promise<boolean> {
|
|
63
|
+
const index = this.tasks.findIndex(t => t.id === taskId);
|
|
64
|
+
if (index !== -1) {
|
|
65
|
+
this.tasks.splice(index, 1);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async updatePriority(taskId: string, priority: number): Promise<boolean> {
|
|
72
|
+
const task = this.tasks.find(t => t.id === taskId);
|
|
73
|
+
if (task) {
|
|
74
|
+
(task as { priority: number }).priority = priority;
|
|
75
|
+
this.tasks.sort((a, b) => b.priority - a.priority);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Task manager implementation
|
|
84
|
+
*/
|
|
85
|
+
export class TaskManager implements ITaskManager {
|
|
86
|
+
private tasks = new Map<string, ITask>();
|
|
87
|
+
private queue: ITaskQueue;
|
|
88
|
+
private metrics = {
|
|
89
|
+
totalTasks: 0,
|
|
90
|
+
completedTasks: 0,
|
|
91
|
+
failedTasks: 0,
|
|
92
|
+
cancelledTasks: 0,
|
|
93
|
+
totalDuration: 0,
|
|
94
|
+
totalWaitTime: 0,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
constructor(
|
|
98
|
+
private eventBus: IEventBus,
|
|
99
|
+
queue?: ITaskQueue,
|
|
100
|
+
) {
|
|
101
|
+
this.queue = queue ?? new TaskQueue();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async createTask(params: ITaskCreate): Promise<ITask> {
|
|
105
|
+
const task: ITask = {
|
|
106
|
+
id: generateSecureTaskId(),
|
|
107
|
+
type: params.type,
|
|
108
|
+
description: params.description,
|
|
109
|
+
priority: params.priority ?? 50,
|
|
110
|
+
createdAt: new Date(),
|
|
111
|
+
status: 'pending',
|
|
112
|
+
timeout: params.timeout,
|
|
113
|
+
assignedAgent: params.assignedAgent,
|
|
114
|
+
input: params.input,
|
|
115
|
+
metadata: params.metadata,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this.tasks.set(task.id, task);
|
|
119
|
+
this.metrics.totalTasks++;
|
|
120
|
+
|
|
121
|
+
this.eventBus.emit(SystemEventTypes.TASK_CREATED, { task });
|
|
122
|
+
|
|
123
|
+
return task;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getTask(taskId: string): ITask | undefined {
|
|
127
|
+
return this.tasks.get(taskId);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getTasks(filter?: Partial<Pick<ITask, 'status' | 'type' | 'assignedAgent'>>): ITask[] {
|
|
131
|
+
let tasks = Array.from(this.tasks.values());
|
|
132
|
+
|
|
133
|
+
if (filter) {
|
|
134
|
+
if (filter.status) {
|
|
135
|
+
tasks = tasks.filter(t => t.status === filter.status);
|
|
136
|
+
}
|
|
137
|
+
if (filter.type) {
|
|
138
|
+
tasks = tasks.filter(t => t.type === filter.type);
|
|
139
|
+
}
|
|
140
|
+
if (filter.assignedAgent) {
|
|
141
|
+
tasks = tasks.filter(t => t.assignedAgent === filter.assignedAgent);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return tasks;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async assignTask(taskId: string, agentId: string): Promise<void> {
|
|
149
|
+
const task = this.tasks.get(taskId);
|
|
150
|
+
if (!task) {
|
|
151
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
task.assignedAgent = agentId;
|
|
155
|
+
task.status = 'assigned';
|
|
156
|
+
|
|
157
|
+
this.eventBus.emit(SystemEventTypes.TASK_ASSIGNED, {
|
|
158
|
+
taskId,
|
|
159
|
+
agentId,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async startTask(taskId: string): Promise<void> {
|
|
164
|
+
const task = this.tasks.get(taskId);
|
|
165
|
+
if (!task) {
|
|
166
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
task.status = 'running';
|
|
170
|
+
task.startedAt = new Date();
|
|
171
|
+
|
|
172
|
+
// Calculate wait time
|
|
173
|
+
const waitTime = task.startedAt.getTime() - task.createdAt.getTime();
|
|
174
|
+
this.metrics.totalWaitTime += waitTime;
|
|
175
|
+
|
|
176
|
+
this.eventBus.emit(SystemEventTypes.TASK_STARTED, {
|
|
177
|
+
taskId,
|
|
178
|
+
agentId: task.assignedAgent,
|
|
179
|
+
startTime: task.startedAt,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async completeTask(taskId: string, result: ITaskResult): Promise<void> {
|
|
184
|
+
const task = this.tasks.get(taskId);
|
|
185
|
+
if (!task) {
|
|
186
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
task.status = 'completed';
|
|
190
|
+
task.completedAt = new Date();
|
|
191
|
+
task.output = result.output;
|
|
192
|
+
|
|
193
|
+
this.metrics.completedTasks++;
|
|
194
|
+
this.metrics.totalDuration += result.duration;
|
|
195
|
+
|
|
196
|
+
this.eventBus.emit(SystemEventTypes.TASK_COMPLETED, {
|
|
197
|
+
taskId,
|
|
198
|
+
result,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async failTask(taskId: string, error: Error): Promise<void> {
|
|
203
|
+
const task = this.tasks.get(taskId);
|
|
204
|
+
if (!task) {
|
|
205
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
task.status = 'failed';
|
|
209
|
+
task.completedAt = new Date();
|
|
210
|
+
task.error = error;
|
|
211
|
+
|
|
212
|
+
this.metrics.failedTasks++;
|
|
213
|
+
|
|
214
|
+
this.eventBus.emit(SystemEventTypes.TASK_FAILED, {
|
|
215
|
+
taskId,
|
|
216
|
+
error,
|
|
217
|
+
retryable: this.isRetryable(task),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async cancelTask(taskId: string, reason?: string): Promise<void> {
|
|
222
|
+
const task = this.tasks.get(taskId);
|
|
223
|
+
if (!task) {
|
|
224
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
task.status = 'cancelled';
|
|
228
|
+
task.completedAt = new Date();
|
|
229
|
+
|
|
230
|
+
this.metrics.cancelledTasks++;
|
|
231
|
+
|
|
232
|
+
this.eventBus.emit(SystemEventTypes.TASK_CANCELLED, {
|
|
233
|
+
taskId,
|
|
234
|
+
reason: reason ?? 'User requested',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async retryTask(taskId: string): Promise<void> {
|
|
239
|
+
const task = this.tasks.get(taskId);
|
|
240
|
+
if (!task) {
|
|
241
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const retryCount = (task.metadata?.retryCount as number) ?? 0;
|
|
245
|
+
const maxRetries = (task.metadata?.maxRetries as number) ?? 3;
|
|
246
|
+
|
|
247
|
+
if (retryCount >= maxRetries) {
|
|
248
|
+
throw new Error(`Task ${taskId} has exceeded max retries`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
task.status = 'pending';
|
|
252
|
+
task.assignedAgent = undefined;
|
|
253
|
+
task.startedAt = undefined;
|
|
254
|
+
task.completedAt = undefined;
|
|
255
|
+
task.error = undefined;
|
|
256
|
+
task.metadata = {
|
|
257
|
+
...task.metadata,
|
|
258
|
+
retryCount: retryCount + 1,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
await this.queue.enqueue(task);
|
|
262
|
+
|
|
263
|
+
this.eventBus.emit(SystemEventTypes.TASK_RETRY, {
|
|
264
|
+
taskId,
|
|
265
|
+
attempt: retryCount + 1,
|
|
266
|
+
maxAttempts: maxRetries,
|
|
267
|
+
error: task.error,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
getMetrics(): TaskManagerMetrics {
|
|
272
|
+
const pendingTasks = this.getTasks({ status: 'pending' }).length;
|
|
273
|
+
const runningTasks = this.getTasks({ status: 'running' }).length;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
totalTasks: this.metrics.totalTasks,
|
|
277
|
+
pendingTasks,
|
|
278
|
+
runningTasks,
|
|
279
|
+
completedTasks: this.metrics.completedTasks,
|
|
280
|
+
failedTasks: this.metrics.failedTasks,
|
|
281
|
+
cancelledTasks: this.metrics.cancelledTasks,
|
|
282
|
+
avgDuration: this.metrics.completedTasks > 0
|
|
283
|
+
? this.metrics.totalDuration / this.metrics.completedTasks
|
|
284
|
+
: 0,
|
|
285
|
+
avgWaitTime: this.metrics.completedTasks > 0
|
|
286
|
+
? this.metrics.totalWaitTime / this.metrics.completedTasks
|
|
287
|
+
: 0,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async cleanup(olderThan: Date): Promise<number> {
|
|
292
|
+
let cleaned = 0;
|
|
293
|
+
const cutoffTime = olderThan.getTime();
|
|
294
|
+
|
|
295
|
+
for (const [taskId, task] of this.tasks.entries()) {
|
|
296
|
+
if (task.completedAt && task.completedAt.getTime() < cutoffTime) {
|
|
297
|
+
this.tasks.delete(taskId);
|
|
298
|
+
cleaned++;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return cleaned;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private isRetryable(task: ITask): boolean {
|
|
306
|
+
const retryCount = (task.metadata?.retryCount as number) ?? 0;
|
|
307
|
+
const maxRetries = (task.metadata?.maxRetries as number) ?? 3;
|
|
308
|
+
return retryCount < maxRetries;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get the task queue
|
|
313
|
+
*/
|
|
314
|
+
getQueue(): ITaskQueue {
|
|
315
|
+
return this.queue;
|
|
316
|
+
}
|
|
317
|
+
}
|