@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.
Files changed (96) hide show
  1. package/README.md +323 -0
  2. package/__tests__/hooks/bash-safety.test.ts +289 -0
  3. package/__tests__/hooks/file-organization.test.ts +335 -0
  4. package/__tests__/hooks/git-commit.test.ts +336 -0
  5. package/__tests__/hooks/index.ts +23 -0
  6. package/__tests__/hooks/session-hooks.test.ts +357 -0
  7. package/__tests__/hooks/task-hooks.test.ts +193 -0
  8. package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
  9. package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
  10. package/docs/EVENTS_README.md +352 -0
  11. package/package.json +39 -0
  12. package/src/core/config/defaults.ts +207 -0
  13. package/src/core/config/index.ts +15 -0
  14. package/src/core/config/loader.ts +271 -0
  15. package/src/core/config/schema.ts +188 -0
  16. package/src/core/config/validator.ts +209 -0
  17. package/src/core/event-bus.ts +236 -0
  18. package/src/core/index.ts +22 -0
  19. package/src/core/interfaces/agent.interface.ts +251 -0
  20. package/src/core/interfaces/coordinator.interface.ts +363 -0
  21. package/src/core/interfaces/event.interface.ts +267 -0
  22. package/src/core/interfaces/index.ts +19 -0
  23. package/src/core/interfaces/memory.interface.ts +332 -0
  24. package/src/core/interfaces/task.interface.ts +223 -0
  25. package/src/core/orchestrator/event-coordinator.ts +122 -0
  26. package/src/core/orchestrator/health-monitor.ts +214 -0
  27. package/src/core/orchestrator/index.ts +89 -0
  28. package/src/core/orchestrator/lifecycle-manager.ts +263 -0
  29. package/src/core/orchestrator/session-manager.ts +279 -0
  30. package/src/core/orchestrator/task-manager.ts +317 -0
  31. package/src/events/domain-events.ts +584 -0
  32. package/src/events/event-store.test.ts +387 -0
  33. package/src/events/event-store.ts +588 -0
  34. package/src/events/example-usage.ts +293 -0
  35. package/src/events/index.ts +90 -0
  36. package/src/events/projections.ts +561 -0
  37. package/src/events/state-reconstructor.ts +349 -0
  38. package/src/events.ts +367 -0
  39. package/src/hooks/INTEGRATION.md +658 -0
  40. package/src/hooks/README.md +532 -0
  41. package/src/hooks/example-usage.ts +499 -0
  42. package/src/hooks/executor.ts +379 -0
  43. package/src/hooks/hooks.test.ts +421 -0
  44. package/src/hooks/index.ts +131 -0
  45. package/src/hooks/registry.ts +333 -0
  46. package/src/hooks/safety/bash-safety.ts +604 -0
  47. package/src/hooks/safety/file-organization.ts +473 -0
  48. package/src/hooks/safety/git-commit.ts +623 -0
  49. package/src/hooks/safety/index.ts +46 -0
  50. package/src/hooks/session-hooks.ts +559 -0
  51. package/src/hooks/task-hooks.ts +513 -0
  52. package/src/hooks/types.ts +357 -0
  53. package/src/hooks/verify-exports.test.ts +125 -0
  54. package/src/index.ts +195 -0
  55. package/src/mcp/connection-pool.ts +438 -0
  56. package/src/mcp/index.ts +183 -0
  57. package/src/mcp/server.ts +774 -0
  58. package/src/mcp/session-manager.ts +428 -0
  59. package/src/mcp/tool-registry.ts +566 -0
  60. package/src/mcp/transport/http.ts +557 -0
  61. package/src/mcp/transport/index.ts +294 -0
  62. package/src/mcp/transport/stdio.ts +324 -0
  63. package/src/mcp/transport/websocket.ts +484 -0
  64. package/src/mcp/types.ts +565 -0
  65. package/src/plugin-interface.ts +663 -0
  66. package/src/plugin-loader.ts +638 -0
  67. package/src/plugin-registry.ts +604 -0
  68. package/src/plugins/index.ts +34 -0
  69. package/src/plugins/official/hive-mind-plugin.ts +330 -0
  70. package/src/plugins/official/index.ts +24 -0
  71. package/src/plugins/official/maestro-plugin.ts +508 -0
  72. package/src/plugins/types.ts +108 -0
  73. package/src/resilience/bulkhead.ts +277 -0
  74. package/src/resilience/circuit-breaker.ts +326 -0
  75. package/src/resilience/index.ts +26 -0
  76. package/src/resilience/rate-limiter.ts +420 -0
  77. package/src/resilience/retry.ts +224 -0
  78. package/src/security/index.ts +39 -0
  79. package/src/security/input-validation.ts +265 -0
  80. package/src/security/secure-random.ts +159 -0
  81. package/src/services/index.ts +16 -0
  82. package/src/services/v3-progress.service.ts +505 -0
  83. package/src/types/agent.types.ts +144 -0
  84. package/src/types/index.ts +22 -0
  85. package/src/types/mcp.types.ts +300 -0
  86. package/src/types/memory.types.ts +263 -0
  87. package/src/types/swarm.types.ts +255 -0
  88. package/src/types/task.types.ts +205 -0
  89. package/src/types.ts +367 -0
  90. package/src/utils/secure-logger.d.ts +69 -0
  91. package/src/utils/secure-logger.d.ts.map +1 -0
  92. package/src/utils/secure-logger.js +208 -0
  93. package/src/utils/secure-logger.js.map +1 -0
  94. package/src/utils/secure-logger.ts +257 -0
  95. package/tmp.json +0 -0
  96. 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
+ }