claude-flow 1.0.0
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/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource manager for preventing conflicts and deadlocks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Resource, CoordinationConfig, SystemEvents } from '../utils/types.ts';
|
|
6
|
+
import { IEventBus } from '../core/event-bus.ts';
|
|
7
|
+
import { ILogger } from '../core/logger.ts';
|
|
8
|
+
import { ResourceLockError } from '../utils/errors.ts';
|
|
9
|
+
import { delay, timeout } from '../utils/helpers.ts';
|
|
10
|
+
|
|
11
|
+
interface LockRequest {
|
|
12
|
+
agentId: string;
|
|
13
|
+
resourceId: string;
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
priority: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resource manager implementation
|
|
20
|
+
*/
|
|
21
|
+
export class ResourceManager {
|
|
22
|
+
private resources = new Map<string, Resource>();
|
|
23
|
+
private locks = new Map<string, string>(); // resourceId -> agentId
|
|
24
|
+
private waitQueue = new Map<string, LockRequest[]>(); // resourceId -> queue
|
|
25
|
+
private agentResources = new Map<string, Set<string>>(); // agentId -> resourceIds
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private config: CoordinationConfig,
|
|
29
|
+
private eventBus: IEventBus,
|
|
30
|
+
private logger: ILogger,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
async initialize(): Promise<void> {
|
|
34
|
+
this.logger.info('Initializing resource manager');
|
|
35
|
+
|
|
36
|
+
// Set up periodic cleanup
|
|
37
|
+
setInterval(() => this.cleanup(), 30000); // Every 30 seconds
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async shutdown(): Promise<void> {
|
|
41
|
+
this.logger.info('Shutting down resource manager');
|
|
42
|
+
|
|
43
|
+
// Release all locks
|
|
44
|
+
for (const [resourceId, agentId] of this.locks) {
|
|
45
|
+
await this.release(resourceId, agentId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.resources.clear();
|
|
49
|
+
this.locks.clear();
|
|
50
|
+
this.waitQueue.clear();
|
|
51
|
+
this.agentResources.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async acquire(resourceId: string, agentId: string, priority = 0): Promise<void> {
|
|
55
|
+
this.logger.debug('Resource acquisition requested', { resourceId, agentId });
|
|
56
|
+
|
|
57
|
+
// Check if resource exists
|
|
58
|
+
if (!this.resources.has(resourceId)) {
|
|
59
|
+
this.resources.set(resourceId, {
|
|
60
|
+
id: resourceId,
|
|
61
|
+
type: 'generic',
|
|
62
|
+
locked: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const resource = this.resources.get(resourceId)!;
|
|
67
|
+
|
|
68
|
+
// Check if already locked by this agent
|
|
69
|
+
if (this.locks.get(resourceId) === agentId) {
|
|
70
|
+
this.logger.debug('Resource already locked by agent', { resourceId, agentId });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Try to acquire lock
|
|
75
|
+
if (!resource.locked) {
|
|
76
|
+
await this.lockResource(resourceId, agentId);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add to wait queue
|
|
81
|
+
const request: LockRequest = {
|
|
82
|
+
agentId,
|
|
83
|
+
resourceId,
|
|
84
|
+
timestamp: new Date(),
|
|
85
|
+
priority,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!this.waitQueue.has(resourceId)) {
|
|
89
|
+
this.waitQueue.set(resourceId, []);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const queue = this.waitQueue.get(resourceId)!;
|
|
93
|
+
queue.push(request);
|
|
94
|
+
|
|
95
|
+
// Sort by priority and timestamp
|
|
96
|
+
queue.sort((a, b) => {
|
|
97
|
+
if (a.priority !== b.priority) {
|
|
98
|
+
return b.priority - a.priority; // Higher priority first
|
|
99
|
+
}
|
|
100
|
+
return a.timestamp.getTime() - b.timestamp.getTime(); // Earlier first
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.logger.info('Agent added to resource wait queue', {
|
|
104
|
+
resourceId,
|
|
105
|
+
agentId,
|
|
106
|
+
queueLength: queue.length,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Wait for resource with timeout
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
while (Date.now() - startTime < this.config.resourceTimeout) {
|
|
112
|
+
// Check if we're next in queue and resource is available
|
|
113
|
+
const nextRequest = queue[0];
|
|
114
|
+
if (nextRequest?.agentId === agentId && !resource.locked) {
|
|
115
|
+
// Remove from queue and acquire
|
|
116
|
+
queue.shift();
|
|
117
|
+
await this.lockResource(resourceId, agentId);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if our request is still in queue
|
|
122
|
+
const ourRequest = queue.find(req => req.agentId === agentId);
|
|
123
|
+
if (!ourRequest) {
|
|
124
|
+
// Request was removed (possibly by cleanup)
|
|
125
|
+
throw new ResourceLockError('Resource request cancelled');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await delay(100);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Timeout - remove from queue
|
|
132
|
+
const index = queue.findIndex(req => req.agentId === agentId);
|
|
133
|
+
if (index !== -1) {
|
|
134
|
+
queue.splice(index, 1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new ResourceLockError(
|
|
138
|
+
`Resource acquisition timeout for ${resourceId}`,
|
|
139
|
+
{ resourceId, agentId, timeout: this.config.resourceTimeout },
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async release(resourceId: string, agentId: string): Promise<void> {
|
|
144
|
+
this.logger.debug('Resource release requested', { resourceId, agentId });
|
|
145
|
+
|
|
146
|
+
const currentLock = this.locks.get(resourceId);
|
|
147
|
+
if (currentLock !== agentId) {
|
|
148
|
+
this.logger.warn('Attempted to release unowned resource', {
|
|
149
|
+
resourceId,
|
|
150
|
+
agentId,
|
|
151
|
+
currentLock,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Release the lock
|
|
157
|
+
this.unlockResource(resourceId, agentId);
|
|
158
|
+
|
|
159
|
+
// Process wait queue
|
|
160
|
+
const queue = this.waitQueue.get(resourceId);
|
|
161
|
+
if (queue && queue.length > 0) {
|
|
162
|
+
const nextRequest = queue.shift()!;
|
|
163
|
+
|
|
164
|
+
// Grant lock to next in queue
|
|
165
|
+
await this.lockResource(resourceId, nextRequest.agentId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async releaseAllForAgent(agentId: string): Promise<void> {
|
|
170
|
+
const resources = this.agentResources.get(agentId);
|
|
171
|
+
if (!resources) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.logger.info('Releasing all resources for agent', {
|
|
176
|
+
agentId,
|
|
177
|
+
resourceCount: resources.size,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const promises = Array.from(resources).map(
|
|
181
|
+
resourceId => this.release(resourceId, agentId),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await Promise.all(promises);
|
|
185
|
+
this.agentResources.delete(agentId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getAllocations(): Map<string, string> {
|
|
189
|
+
return new Map(this.locks);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getWaitingRequests(): Map<string, string[]> {
|
|
193
|
+
const waiting = new Map<string, string[]>();
|
|
194
|
+
|
|
195
|
+
for (const [resourceId, queue] of this.waitQueue) {
|
|
196
|
+
if (queue.length > 0) {
|
|
197
|
+
waiting.set(
|
|
198
|
+
queue[0].agentId,
|
|
199
|
+
[...(waiting.get(queue[0].agentId) || []), resourceId],
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return waiting;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async getHealthStatus(): Promise<{
|
|
208
|
+
healthy: boolean;
|
|
209
|
+
error?: string;
|
|
210
|
+
metrics?: Record<string, number>;
|
|
211
|
+
}> {
|
|
212
|
+
const totalResources = this.resources.size;
|
|
213
|
+
const lockedResources = this.locks.size;
|
|
214
|
+
const waitingAgents = new Set<string>();
|
|
215
|
+
let totalWaiting = 0;
|
|
216
|
+
|
|
217
|
+
for (const queue of this.waitQueue.values()) {
|
|
218
|
+
totalWaiting += queue.length;
|
|
219
|
+
queue.forEach(req => waitingAgents.add(req.agentId));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
healthy: true,
|
|
224
|
+
metrics: {
|
|
225
|
+
totalResources,
|
|
226
|
+
lockedResources,
|
|
227
|
+
freeResources: totalResources - lockedResources,
|
|
228
|
+
waitingAgents: waitingAgents.size,
|
|
229
|
+
totalWaitingRequests: totalWaiting,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async lockResource(resourceId: string, agentId: string): Promise<void> {
|
|
235
|
+
const resource = this.resources.get(resourceId)!;
|
|
236
|
+
|
|
237
|
+
resource.locked = true;
|
|
238
|
+
resource.lockedBy = agentId;
|
|
239
|
+
resource.lockedAt = new Date();
|
|
240
|
+
|
|
241
|
+
this.locks.set(resourceId, agentId);
|
|
242
|
+
|
|
243
|
+
// Track agent resources
|
|
244
|
+
if (!this.agentResources.has(agentId)) {
|
|
245
|
+
this.agentResources.set(agentId, new Set());
|
|
246
|
+
}
|
|
247
|
+
this.agentResources.get(agentId)!.add(resourceId);
|
|
248
|
+
|
|
249
|
+
this.logger.info('Resource locked', { resourceId, agentId });
|
|
250
|
+
|
|
251
|
+
// Emit event
|
|
252
|
+
this.eventBus.emit(SystemEvents.RESOURCE_ACQUIRED, { resourceId, agentId });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private unlockResource(resourceId: string, agentId: string): void {
|
|
256
|
+
const resource = this.resources.get(resourceId);
|
|
257
|
+
if (!resource) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
resource.locked = false;
|
|
262
|
+
delete resource.lockedBy;
|
|
263
|
+
delete resource.lockedAt;
|
|
264
|
+
|
|
265
|
+
this.locks.delete(resourceId);
|
|
266
|
+
|
|
267
|
+
// Remove from agent resources
|
|
268
|
+
this.agentResources.get(agentId)?.delete(resourceId);
|
|
269
|
+
|
|
270
|
+
this.logger.info('Resource unlocked', { resourceId, agentId });
|
|
271
|
+
|
|
272
|
+
// Emit event
|
|
273
|
+
this.eventBus.emit(SystemEvents.RESOURCE_RELEASED, { resourceId, agentId });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async performMaintenance(): Promise<void> {
|
|
277
|
+
this.logger.debug('Performing resource manager maintenance');
|
|
278
|
+
this.cleanup();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private cleanup(): void {
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
|
|
284
|
+
// Clean up stale wait requests
|
|
285
|
+
for (const [resourceId, queue] of this.waitQueue) {
|
|
286
|
+
const filtered = queue.filter(req => {
|
|
287
|
+
const age = now - req.timestamp.getTime();
|
|
288
|
+
if (age > this.config.resourceTimeout) {
|
|
289
|
+
this.logger.warn('Removing stale resource request', {
|
|
290
|
+
resourceId,
|
|
291
|
+
agentId: req.agentId,
|
|
292
|
+
age,
|
|
293
|
+
});
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (filtered.length === 0) {
|
|
300
|
+
this.waitQueue.delete(resourceId);
|
|
301
|
+
} else {
|
|
302
|
+
this.waitQueue.set(resourceId, filtered);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Clean up locks held too long
|
|
307
|
+
for (const [resourceId, agentId] of this.locks) {
|
|
308
|
+
const resource = this.resources.get(resourceId);
|
|
309
|
+
if (resource?.lockedAt) {
|
|
310
|
+
const lockAge = now - resource.lockedAt.getTime();
|
|
311
|
+
if (lockAge > this.config.resourceTimeout * 2) {
|
|
312
|
+
this.logger.warn('Force releasing stale lock', {
|
|
313
|
+
resourceId,
|
|
314
|
+
agentId,
|
|
315
|
+
lockAge,
|
|
316
|
+
});
|
|
317
|
+
this.unlockResource(resourceId, agentId);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task scheduler implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Task, TaskStatus, CoordinationConfig, SystemEvents } from '../utils/types.ts';
|
|
6
|
+
import { IEventBus } from '../core/event-bus.ts';
|
|
7
|
+
import { ILogger } from '../core/logger.ts';
|
|
8
|
+
import { TaskError, TaskTimeoutError, TaskDependencyError } from '../utils/errors.ts';
|
|
9
|
+
import { delay } from '../utils/helpers.ts';
|
|
10
|
+
|
|
11
|
+
interface ScheduledTask {
|
|
12
|
+
task: Task;
|
|
13
|
+
agentId: string;
|
|
14
|
+
attempts: number;
|
|
15
|
+
lastAttempt?: Date;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Task scheduler for managing task assignment and execution
|
|
21
|
+
*/
|
|
22
|
+
export class TaskScheduler {
|
|
23
|
+
protected tasks = new Map<string, ScheduledTask>();
|
|
24
|
+
protected agentTasks = new Map<string, Set<string>>(); // agentId -> taskIds
|
|
25
|
+
protected taskDependencies = new Map<string, Set<string>>(); // taskId -> dependent taskIds
|
|
26
|
+
protected completedTasks = new Set<string>();
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
protected config: CoordinationConfig,
|
|
30
|
+
protected eventBus: IEventBus,
|
|
31
|
+
protected logger: ILogger,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async initialize(): Promise<void> {
|
|
35
|
+
this.logger.info('Initializing task scheduler');
|
|
36
|
+
|
|
37
|
+
// Set up periodic cleanup
|
|
38
|
+
setInterval(() => this.cleanup(), 60000); // Every minute
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async shutdown(): Promise<void> {
|
|
42
|
+
this.logger.info('Shutting down task scheduler');
|
|
43
|
+
|
|
44
|
+
// Cancel all active tasks
|
|
45
|
+
const taskIds = Array.from(this.tasks.keys());
|
|
46
|
+
await Promise.all(taskIds.map(id => this.cancelTask(id, 'Scheduler shutdown')));
|
|
47
|
+
|
|
48
|
+
this.tasks.clear();
|
|
49
|
+
this.agentTasks.clear();
|
|
50
|
+
this.taskDependencies.clear();
|
|
51
|
+
this.completedTasks.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async assignTask(task: Task, agentId: string): Promise<void> {
|
|
55
|
+
this.logger.info('Assigning task', { taskId: task.id, agentId });
|
|
56
|
+
|
|
57
|
+
// Check dependencies
|
|
58
|
+
if (task.dependencies.length > 0) {
|
|
59
|
+
const unmetDependencies = task.dependencies.filter(
|
|
60
|
+
depId => !this.completedTasks.has(depId),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (unmetDependencies.length > 0) {
|
|
64
|
+
throw new TaskDependencyError(task.id, unmetDependencies);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create scheduled task
|
|
69
|
+
const scheduledTask: ScheduledTask = {
|
|
70
|
+
task: { ...task, status: 'assigned', assignedAgent: agentId },
|
|
71
|
+
agentId,
|
|
72
|
+
attempts: 0,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Store task
|
|
76
|
+
this.tasks.set(task.id, scheduledTask);
|
|
77
|
+
|
|
78
|
+
// Update agent tasks
|
|
79
|
+
if (!this.agentTasks.has(agentId)) {
|
|
80
|
+
this.agentTasks.set(agentId, new Set());
|
|
81
|
+
}
|
|
82
|
+
this.agentTasks.get(agentId)!.add(task.id);
|
|
83
|
+
|
|
84
|
+
// Update dependencies
|
|
85
|
+
for (const depId of task.dependencies) {
|
|
86
|
+
if (!this.taskDependencies.has(depId)) {
|
|
87
|
+
this.taskDependencies.set(depId, new Set());
|
|
88
|
+
}
|
|
89
|
+
this.taskDependencies.get(depId)!.add(task.id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Start task execution
|
|
93
|
+
this.startTask(task.id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async completeTask(taskId: string, result: unknown): Promise<void> {
|
|
97
|
+
const scheduled = this.tasks.get(taskId);
|
|
98
|
+
if (!scheduled) {
|
|
99
|
+
throw new TaskError(`Task not found: ${taskId}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.logger.info('Task completed', { taskId, agentId: scheduled.agentId });
|
|
103
|
+
|
|
104
|
+
// Update task status
|
|
105
|
+
scheduled.task.status = 'completed';
|
|
106
|
+
scheduled.task.output = result as Record<string, unknown>;
|
|
107
|
+
scheduled.task.completedAt = new Date();
|
|
108
|
+
|
|
109
|
+
// Clear timeout
|
|
110
|
+
if (scheduled.timeout) {
|
|
111
|
+
clearTimeout(scheduled.timeout);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Remove from active tasks
|
|
115
|
+
this.tasks.delete(taskId);
|
|
116
|
+
this.agentTasks.get(scheduled.agentId)?.delete(taskId);
|
|
117
|
+
|
|
118
|
+
// Add to completed tasks
|
|
119
|
+
this.completedTasks.add(taskId);
|
|
120
|
+
|
|
121
|
+
// Check and start dependent tasks
|
|
122
|
+
const dependents = this.taskDependencies.get(taskId);
|
|
123
|
+
if (dependents) {
|
|
124
|
+
for (const dependentId of dependents) {
|
|
125
|
+
const dependent = this.tasks.get(dependentId);
|
|
126
|
+
if (dependent && this.canStartTask(dependent.task)) {
|
|
127
|
+
this.startTask(dependentId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async failTask(taskId: string, error: Error): Promise<void> {
|
|
134
|
+
const scheduled = this.tasks.get(taskId);
|
|
135
|
+
if (!scheduled) {
|
|
136
|
+
throw new TaskError(`Task not found: ${taskId}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.logger.error('Task failed', {
|
|
140
|
+
taskId,
|
|
141
|
+
agentId: scheduled.agentId,
|
|
142
|
+
attempt: scheduled.attempts,
|
|
143
|
+
error,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Clear timeout
|
|
147
|
+
if (scheduled.timeout) {
|
|
148
|
+
clearTimeout(scheduled.timeout);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
scheduled.attempts++;
|
|
152
|
+
scheduled.lastAttempt = new Date();
|
|
153
|
+
|
|
154
|
+
// Check if we should retry
|
|
155
|
+
if (scheduled.attempts < this.config.maxRetries) {
|
|
156
|
+
this.logger.info('Retrying task', {
|
|
157
|
+
taskId,
|
|
158
|
+
attempt: scheduled.attempts,
|
|
159
|
+
maxRetries: this.config.maxRetries,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Schedule retry with exponential backoff
|
|
163
|
+
const retryDelay = this.config.retryDelay * Math.pow(2, scheduled.attempts - 1);
|
|
164
|
+
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
this.startTask(taskId);
|
|
167
|
+
}, retryDelay);
|
|
168
|
+
} else {
|
|
169
|
+
// Max retries exceeded, mark as failed
|
|
170
|
+
scheduled.task.status = 'failed';
|
|
171
|
+
scheduled.task.error = error;
|
|
172
|
+
scheduled.task.completedAt = new Date();
|
|
173
|
+
|
|
174
|
+
// Remove from active tasks
|
|
175
|
+
this.tasks.delete(taskId);
|
|
176
|
+
this.agentTasks.get(scheduled.agentId)?.delete(taskId);
|
|
177
|
+
|
|
178
|
+
// Cancel dependent tasks
|
|
179
|
+
await this.cancelDependentTasks(taskId, 'Parent task failed');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async cancelTask(taskId: string, reason: string): Promise<void> {
|
|
184
|
+
const scheduled = this.tasks.get(taskId);
|
|
185
|
+
if (!scheduled) {
|
|
186
|
+
return; // Already cancelled or completed
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.logger.info('Cancelling task', { taskId, reason });
|
|
190
|
+
|
|
191
|
+
// Clear timeout
|
|
192
|
+
if (scheduled.timeout) {
|
|
193
|
+
clearTimeout(scheduled.timeout);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update task status
|
|
197
|
+
scheduled.task.status = 'cancelled';
|
|
198
|
+
scheduled.task.completedAt = new Date();
|
|
199
|
+
|
|
200
|
+
// Emit cancellation event
|
|
201
|
+
this.eventBus.emit(SystemEvents.TASK_CANCELLED, { taskId, reason });
|
|
202
|
+
|
|
203
|
+
// Remove from active tasks
|
|
204
|
+
this.tasks.delete(taskId);
|
|
205
|
+
this.agentTasks.get(scheduled.agentId)?.delete(taskId);
|
|
206
|
+
|
|
207
|
+
// Cancel dependent tasks
|
|
208
|
+
await this.cancelDependentTasks(taskId, 'Parent task cancelled');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async cancelAgentTasks(agentId: string): Promise<void> {
|
|
212
|
+
const taskIds = this.agentTasks.get(agentId);
|
|
213
|
+
if (!taskIds) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.logger.info('Cancelling all tasks for agent', {
|
|
218
|
+
agentId,
|
|
219
|
+
taskCount: taskIds.size,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const promises = Array.from(taskIds).map(
|
|
223
|
+
taskId => this.cancelTask(taskId, 'Agent terminated'),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
await Promise.all(promises);
|
|
227
|
+
this.agentTasks.delete(agentId);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async rescheduleAgentTasks(agentId: string): Promise<void> {
|
|
231
|
+
const taskIds = this.agentTasks.get(agentId);
|
|
232
|
+
if (!taskIds || taskIds.size === 0) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.logger.info('Rescheduling tasks for agent', {
|
|
237
|
+
agentId,
|
|
238
|
+
taskCount: taskIds.size,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
for (const taskId of taskIds) {
|
|
242
|
+
const scheduled = this.tasks.get(taskId);
|
|
243
|
+
if (scheduled && scheduled.task.status === 'running') {
|
|
244
|
+
// Reset task status
|
|
245
|
+
scheduled.task.status = 'queued';
|
|
246
|
+
scheduled.attempts = 0;
|
|
247
|
+
|
|
248
|
+
// Re-emit task created event for reassignment
|
|
249
|
+
this.eventBus.emit(SystemEvents.TASK_CREATED, {
|
|
250
|
+
task: scheduled.task,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getAgentTaskCount(agentId: string): number {
|
|
257
|
+
return this.agentTasks.get(agentId)?.size || 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async getHealthStatus(): Promise<{
|
|
261
|
+
healthy: boolean;
|
|
262
|
+
error?: string;
|
|
263
|
+
metrics?: Record<string, number>;
|
|
264
|
+
}> {
|
|
265
|
+
const activeTasks = this.tasks.size;
|
|
266
|
+
const completedTasks = this.completedTasks.size;
|
|
267
|
+
const agentsWithTasks = this.agentTasks.size;
|
|
268
|
+
|
|
269
|
+
const tasksByStatus: Record<TaskStatus, number> = {
|
|
270
|
+
pending: 0,
|
|
271
|
+
queued: 0,
|
|
272
|
+
assigned: 0,
|
|
273
|
+
running: 0,
|
|
274
|
+
completed: completedTasks,
|
|
275
|
+
failed: 0,
|
|
276
|
+
cancelled: 0,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for (const scheduled of this.tasks.values()) {
|
|
280
|
+
tasksByStatus[scheduled.task.status]++;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
healthy: true,
|
|
285
|
+
metrics: {
|
|
286
|
+
activeTasks,
|
|
287
|
+
completedTasks,
|
|
288
|
+
agentsWithTasks,
|
|
289
|
+
...tasksByStatus,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async getAgentTasks(agentId: string): Promise<Task[]> {
|
|
295
|
+
const taskIds = this.agentTasks.get(agentId);
|
|
296
|
+
if (!taskIds) {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const tasks: Task[] = [];
|
|
301
|
+
for (const taskId of taskIds) {
|
|
302
|
+
const scheduled = this.tasks.get(taskId);
|
|
303
|
+
if (scheduled) {
|
|
304
|
+
tasks.push(scheduled.task);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return tasks;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async performMaintenance(): Promise<void> {
|
|
312
|
+
this.logger.debug('Performing task scheduler maintenance');
|
|
313
|
+
|
|
314
|
+
// Cleanup old completed tasks
|
|
315
|
+
this.cleanup();
|
|
316
|
+
|
|
317
|
+
// Check for stuck tasks
|
|
318
|
+
const now = new Date();
|
|
319
|
+
for (const [taskId, scheduled] of this.tasks) {
|
|
320
|
+
if (scheduled.task.status === 'running' && scheduled.task.startedAt) {
|
|
321
|
+
const runtime = now.getTime() - scheduled.task.startedAt.getTime();
|
|
322
|
+
if (runtime > this.config.resourceTimeout * 2) {
|
|
323
|
+
this.logger.warn('Found stuck task', {
|
|
324
|
+
taskId,
|
|
325
|
+
runtime,
|
|
326
|
+
agentId: scheduled.agentId,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Force fail the task
|
|
330
|
+
await this.failTask(taskId, new TaskTimeoutError(taskId, runtime));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private startTask(taskId: string): void {
|
|
337
|
+
const scheduled = this.tasks.get(taskId);
|
|
338
|
+
if (!scheduled) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Update status
|
|
343
|
+
scheduled.task.status = 'running';
|
|
344
|
+
scheduled.task.startedAt = new Date();
|
|
345
|
+
|
|
346
|
+
// Emit task started event
|
|
347
|
+
this.eventBus.emit(SystemEvents.TASK_STARTED, {
|
|
348
|
+
taskId,
|
|
349
|
+
agentId: scheduled.agentId,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Set timeout for task execution
|
|
353
|
+
const timeoutMs = this.config.resourceTimeout;
|
|
354
|
+
scheduled.timeout = setTimeout(() => {
|
|
355
|
+
this.failTask(taskId, new TaskTimeoutError(taskId, timeoutMs));
|
|
356
|
+
}, timeoutMs);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private canStartTask(task: Task): boolean {
|
|
360
|
+
// Check if all dependencies are completed
|
|
361
|
+
return task.dependencies.every(depId => this.completedTasks.has(depId));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private async cancelDependentTasks(taskId: string, reason: string): Promise<void> {
|
|
365
|
+
const dependents = this.taskDependencies.get(taskId);
|
|
366
|
+
if (!dependents) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const dependentId of dependents) {
|
|
371
|
+
await this.cancelTask(dependentId, reason);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private cleanup(): void {
|
|
376
|
+
// Clean up old completed tasks (keep last 1000)
|
|
377
|
+
if (this.completedTasks.size > 1000) {
|
|
378
|
+
const toRemove = this.completedTasks.size - 1000;
|
|
379
|
+
const iterator = this.completedTasks.values();
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < toRemove; i++) {
|
|
382
|
+
const result = iterator.next();
|
|
383
|
+
if (!result.done && result.value) {
|
|
384
|
+
this.completedTasks.delete(result.value);
|
|
385
|
+
this.taskDependencies.delete(result.value);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|