@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,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Health Monitor
|
|
3
|
+
* Decomposed from orchestrator.ts - Agent health checks
|
|
4
|
+
* ~150 lines (target achieved)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
IHealthMonitor,
|
|
9
|
+
IHealthStatus,
|
|
10
|
+
IComponentHealth,
|
|
11
|
+
} from '../interfaces/coordinator.interface.js';
|
|
12
|
+
import type { IEventBus } from '../interfaces/event.interface.js';
|
|
13
|
+
import { SystemEventTypes } from '../interfaces/event.interface.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Health check function type
|
|
17
|
+
*/
|
|
18
|
+
export type HealthCheckFn = () => Promise<{
|
|
19
|
+
healthy: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
metrics?: Record<string, number>;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Health monitor configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface HealthMonitorConfig {
|
|
28
|
+
checkInterval: number;
|
|
29
|
+
historyLimit: number;
|
|
30
|
+
degradedThreshold: number;
|
|
31
|
+
unhealthyThreshold: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Health monitor implementation
|
|
36
|
+
*/
|
|
37
|
+
export class HealthMonitor implements IHealthMonitor {
|
|
38
|
+
private checks = new Map<string, HealthCheckFn>();
|
|
39
|
+
private history: IHealthStatus[] = [];
|
|
40
|
+
private interval?: ReturnType<typeof setInterval>;
|
|
41
|
+
private listeners: Array<(status: IHealthStatus) => void> = [];
|
|
42
|
+
private running = false;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
private eventBus: IEventBus,
|
|
46
|
+
private config: HealthMonitorConfig = {
|
|
47
|
+
checkInterval: 30000,
|
|
48
|
+
historyLimit: 100,
|
|
49
|
+
degradedThreshold: 1,
|
|
50
|
+
unhealthyThreshold: 2,
|
|
51
|
+
},
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
start(): void {
|
|
55
|
+
if (this.running) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.running = true;
|
|
60
|
+
this.interval = setInterval(async () => {
|
|
61
|
+
const status = await this.getStatus();
|
|
62
|
+
this.addToHistory(status);
|
|
63
|
+
this.notifyListeners(status);
|
|
64
|
+
this.eventBus.emit(SystemEventTypes.SYSTEM_HEALTHCHECK, { status });
|
|
65
|
+
}, this.config.checkInterval);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
stop(): void {
|
|
69
|
+
if (this.interval) {
|
|
70
|
+
clearInterval(this.interval);
|
|
71
|
+
this.interval = undefined;
|
|
72
|
+
}
|
|
73
|
+
this.running = false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async getStatus(): Promise<IHealthStatus> {
|
|
77
|
+
const components: Record<string, IComponentHealth> = {};
|
|
78
|
+
let unhealthyCount = 0;
|
|
79
|
+
let degradedCount = 0;
|
|
80
|
+
|
|
81
|
+
const checkPromises = Array.from(this.checks.entries()).map(
|
|
82
|
+
async ([name, check]) => {
|
|
83
|
+
try {
|
|
84
|
+
const result = await Promise.race([
|
|
85
|
+
check(),
|
|
86
|
+
this.timeout(5000, 'Health check timeout'),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const health: IComponentHealth = {
|
|
90
|
+
name,
|
|
91
|
+
status: result.healthy ? 'healthy' : 'unhealthy',
|
|
92
|
+
lastCheck: new Date(),
|
|
93
|
+
error: result.error,
|
|
94
|
+
metrics: result.metrics,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return { name, health };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
health: {
|
|
102
|
+
name,
|
|
103
|
+
status: 'unhealthy' as const,
|
|
104
|
+
lastCheck: new Date(),
|
|
105
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const results = await Promise.allSettled(checkPromises);
|
|
113
|
+
|
|
114
|
+
for (const result of results) {
|
|
115
|
+
if (result.status === 'fulfilled') {
|
|
116
|
+
const { name, health } = result.value;
|
|
117
|
+
components[name] = health;
|
|
118
|
+
|
|
119
|
+
if (health.status === 'unhealthy') {
|
|
120
|
+
unhealthyCount++;
|
|
121
|
+
} else if (health.status === 'degraded') {
|
|
122
|
+
degradedCount++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let overallStatus: IHealthStatus['status'] = 'healthy';
|
|
128
|
+
if (unhealthyCount >= this.config.unhealthyThreshold) {
|
|
129
|
+
overallStatus = 'unhealthy';
|
|
130
|
+
} else if (
|
|
131
|
+
unhealthyCount > 0 ||
|
|
132
|
+
degradedCount >= this.config.degradedThreshold
|
|
133
|
+
) {
|
|
134
|
+
overallStatus = 'degraded';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
status: overallStatus,
|
|
139
|
+
components,
|
|
140
|
+
timestamp: new Date(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
registerCheck(name: string, check: HealthCheckFn): void {
|
|
145
|
+
this.checks.set(name, check);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
unregisterCheck(name: string): void {
|
|
149
|
+
this.checks.delete(name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getHistory(limit?: number): IHealthStatus[] {
|
|
153
|
+
const count = limit ?? this.config.historyLimit;
|
|
154
|
+
return this.history.slice(-count);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
onHealthChange(callback: (status: IHealthStatus) => void): () => void {
|
|
158
|
+
this.listeners.push(callback);
|
|
159
|
+
return () => {
|
|
160
|
+
const index = this.listeners.indexOf(callback);
|
|
161
|
+
if (index !== -1) {
|
|
162
|
+
this.listeners.splice(index, 1);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private addToHistory(status: IHealthStatus): void {
|
|
168
|
+
this.history.push(status);
|
|
169
|
+
|
|
170
|
+
// Trim history to limit
|
|
171
|
+
if (this.history.length > this.config.historyLimit) {
|
|
172
|
+
this.history = this.history.slice(-this.config.historyLimit);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private notifyListeners(status: IHealthStatus): void {
|
|
177
|
+
for (const listener of this.listeners) {
|
|
178
|
+
try {
|
|
179
|
+
listener(status);
|
|
180
|
+
} catch {
|
|
181
|
+
// Ignore listener errors
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private timeout(ms: number, message: string): Promise<never> {
|
|
187
|
+
return new Promise((_, reject) => {
|
|
188
|
+
setTimeout(() => reject(new Error(message)), ms);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get component health by name
|
|
194
|
+
*/
|
|
195
|
+
async getComponentHealth(name: string): Promise<IComponentHealth | undefined> {
|
|
196
|
+
const status = await this.getStatus();
|
|
197
|
+
return status.components[name];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if system is healthy
|
|
202
|
+
*/
|
|
203
|
+
async isHealthy(): Promise<boolean> {
|
|
204
|
+
const status = await this.getStatus();
|
|
205
|
+
return status.status === 'healthy';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get registered check names
|
|
210
|
+
*/
|
|
211
|
+
getRegisteredChecks(): string[] {
|
|
212
|
+
return Array.from(this.checks.keys());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Orchestrator Facade
|
|
3
|
+
* Unified interface to decomposed orchestrator components
|
|
4
|
+
* ~50 lines (target achieved)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IOrchestrator, IHealthStatus, IOrchestratorMetrics } from '../interfaces/coordinator.interface.js';
|
|
8
|
+
import type { ITask, ITaskCreate, ITaskResult } from '../interfaces/task.interface.js';
|
|
9
|
+
import type { IAgent, IAgentConfig } from '../interfaces/agent.interface.js';
|
|
10
|
+
import type { IEventBus } from '../interfaces/event.interface.js';
|
|
11
|
+
|
|
12
|
+
import { TaskManager } from './task-manager.js';
|
|
13
|
+
import { SessionManager, type ISessionManager, type SessionManagerConfig } from './session-manager.js';
|
|
14
|
+
import { HealthMonitor, type HealthMonitorConfig } from './health-monitor.js';
|
|
15
|
+
import { LifecycleManager, type LifecycleManagerConfig } from './lifecycle-manager.js';
|
|
16
|
+
import { EventCoordinator } from './event-coordinator.js';
|
|
17
|
+
import { EventBus } from '../event-bus.js';
|
|
18
|
+
|
|
19
|
+
export * from './task-manager.js';
|
|
20
|
+
export * from './session-manager.js';
|
|
21
|
+
export * from './health-monitor.js';
|
|
22
|
+
export * from './lifecycle-manager.js';
|
|
23
|
+
export * from './event-coordinator.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Orchestrator facade configuration
|
|
27
|
+
* (Note: For schema-validated config, use OrchestratorConfig from config/schema.ts)
|
|
28
|
+
*/
|
|
29
|
+
export interface OrchestratorFacadeConfig {
|
|
30
|
+
session: SessionManagerConfig;
|
|
31
|
+
health: HealthMonitorConfig;
|
|
32
|
+
lifecycle: LifecycleManagerConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default orchestrator facade configuration
|
|
37
|
+
*/
|
|
38
|
+
export const defaultOrchestratorFacadeConfig: OrchestratorFacadeConfig = {
|
|
39
|
+
session: {
|
|
40
|
+
persistSessions: true,
|
|
41
|
+
dataDir: './data',
|
|
42
|
+
sessionRetentionMs: 3600000,
|
|
43
|
+
},
|
|
44
|
+
health: {
|
|
45
|
+
checkInterval: 30000,
|
|
46
|
+
historyLimit: 100,
|
|
47
|
+
degradedThreshold: 1,
|
|
48
|
+
unhealthyThreshold: 2,
|
|
49
|
+
},
|
|
50
|
+
lifecycle: {
|
|
51
|
+
maxConcurrentAgents: 20,
|
|
52
|
+
spawnTimeout: 30000,
|
|
53
|
+
terminateTimeout: 10000,
|
|
54
|
+
maxSpawnRetries: 3,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create orchestrator components
|
|
60
|
+
*/
|
|
61
|
+
export function createOrchestrator(config: Partial<OrchestratorFacadeConfig> = {}) {
|
|
62
|
+
const mergedConfig: OrchestratorFacadeConfig = {
|
|
63
|
+
session: { ...defaultOrchestratorFacadeConfig.session, ...config.session },
|
|
64
|
+
health: { ...defaultOrchestratorFacadeConfig.health, ...config.health },
|
|
65
|
+
lifecycle: { ...defaultOrchestratorFacadeConfig.lifecycle, ...config.lifecycle },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const eventBus = new EventBus();
|
|
69
|
+
const taskManager = new TaskManager(eventBus);
|
|
70
|
+
const sessionManager = new SessionManager(eventBus, mergedConfig.session);
|
|
71
|
+
const healthMonitor = new HealthMonitor(eventBus, mergedConfig.health);
|
|
72
|
+
const lifecycleManager = new LifecycleManager(eventBus, mergedConfig.lifecycle);
|
|
73
|
+
const eventCoordinator = new EventCoordinator(eventBus);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
eventBus,
|
|
77
|
+
taskManager,
|
|
78
|
+
sessionManager,
|
|
79
|
+
healthMonitor,
|
|
80
|
+
lifecycleManager,
|
|
81
|
+
eventCoordinator,
|
|
82
|
+
config: mergedConfig,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Orchestrator type for facade
|
|
88
|
+
*/
|
|
89
|
+
export type OrchestratorComponents = ReturnType<typeof createOrchestrator>;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Lifecycle Manager
|
|
3
|
+
* Decomposed from orchestrator.ts - Agent spawn/terminate
|
|
4
|
+
* ~150 lines (target achieved)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
IAgent,
|
|
9
|
+
IAgentConfig,
|
|
10
|
+
IAgentLifecycleManager,
|
|
11
|
+
IAgentPool,
|
|
12
|
+
AgentStatus,
|
|
13
|
+
} from '../interfaces/agent.interface.js';
|
|
14
|
+
import type { IEventBus } from '../interfaces/event.interface.js';
|
|
15
|
+
import { SystemEventTypes } from '../interfaces/event.interface.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Agent pool implementation
|
|
19
|
+
*/
|
|
20
|
+
export class AgentPool implements IAgentPool {
|
|
21
|
+
private agents = new Map<string, IAgent>();
|
|
22
|
+
|
|
23
|
+
add(agent: IAgent): void {
|
|
24
|
+
this.agents.set(agent.id, agent);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
remove(agentId: string): boolean {
|
|
28
|
+
return this.agents.delete(agentId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get(agentId: string): IAgent | undefined {
|
|
32
|
+
return this.agents.get(agentId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getAll(): IAgent[] {
|
|
36
|
+
return Array.from(this.agents.values());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getByStatus(status: AgentStatus): IAgent[] {
|
|
40
|
+
return this.getAll().filter(agent => agent.status === status);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getByType(type: string): IAgent[] {
|
|
44
|
+
return this.getAll().filter(agent => agent.type === type);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getAvailable(): IAgent[] {
|
|
48
|
+
return this.getAll().filter(
|
|
49
|
+
agent =>
|
|
50
|
+
(agent.status === 'active' || agent.status === 'idle') &&
|
|
51
|
+
agent.currentTaskCount < agent.config.maxConcurrentTasks,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
size(): number {
|
|
56
|
+
return this.agents.size;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
hasCapacity(maxSize: number): boolean {
|
|
60
|
+
return this.agents.size < maxSize;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
clear(): void {
|
|
64
|
+
this.agents.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Lifecycle manager configuration
|
|
70
|
+
*/
|
|
71
|
+
export interface LifecycleManagerConfig {
|
|
72
|
+
maxConcurrentAgents: number;
|
|
73
|
+
spawnTimeout: number;
|
|
74
|
+
terminateTimeout: number;
|
|
75
|
+
maxSpawnRetries: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Lifecycle manager implementation
|
|
80
|
+
*/
|
|
81
|
+
export class LifecycleManager implements IAgentLifecycleManager {
|
|
82
|
+
private pool: IAgentPool;
|
|
83
|
+
|
|
84
|
+
constructor(
|
|
85
|
+
private eventBus: IEventBus,
|
|
86
|
+
private config: LifecycleManagerConfig,
|
|
87
|
+
pool?: IAgentPool,
|
|
88
|
+
) {
|
|
89
|
+
this.pool = pool ?? new AgentPool();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async spawn(config: IAgentConfig): Promise<IAgent> {
|
|
93
|
+
// Validate capacity
|
|
94
|
+
if (!this.pool.hasCapacity(this.config.maxConcurrentAgents)) {
|
|
95
|
+
throw new Error('Maximum concurrent agents reached');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate agent doesn't already exist
|
|
99
|
+
if (this.pool.get(config.id)) {
|
|
100
|
+
throw new Error(`Agent with ID ${config.id} already exists`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const agent: IAgent = {
|
|
104
|
+
id: config.id,
|
|
105
|
+
name: config.name,
|
|
106
|
+
type: config.type,
|
|
107
|
+
config,
|
|
108
|
+
createdAt: new Date(),
|
|
109
|
+
status: 'spawning',
|
|
110
|
+
currentTaskCount: 0,
|
|
111
|
+
lastActivity: new Date(),
|
|
112
|
+
metrics: {
|
|
113
|
+
tasksCompleted: 0,
|
|
114
|
+
tasksFailed: 0,
|
|
115
|
+
avgTaskDuration: 0,
|
|
116
|
+
errorCount: 0,
|
|
117
|
+
uptime: 0,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Add to pool
|
|
122
|
+
this.pool.add(agent);
|
|
123
|
+
|
|
124
|
+
// Mark as active
|
|
125
|
+
agent.status = 'active';
|
|
126
|
+
|
|
127
|
+
this.eventBus.emit(SystemEventTypes.AGENT_SPAWNED, {
|
|
128
|
+
agentId: agent.id,
|
|
129
|
+
profile: config,
|
|
130
|
+
sessionId: undefined,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return agent;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async spawnBatch(configs: IAgentConfig[]): Promise<Map<string, IAgent>> {
|
|
137
|
+
const results = new Map<string, IAgent>();
|
|
138
|
+
|
|
139
|
+
// Check total capacity
|
|
140
|
+
if (this.pool.size() + configs.length > this.config.maxConcurrentAgents) {
|
|
141
|
+
throw new Error('Batch would exceed maximum concurrent agents');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Spawn in parallel
|
|
145
|
+
const spawnPromises = configs.map(async config => {
|
|
146
|
+
try {
|
|
147
|
+
const agent = await this.spawn(config);
|
|
148
|
+
return { id: config.id, agent, error: null };
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return { id: config.id, agent: null, error };
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const settled = await Promise.allSettled(spawnPromises);
|
|
155
|
+
|
|
156
|
+
for (const result of settled) {
|
|
157
|
+
if (result.status === 'fulfilled' && result.value.agent) {
|
|
158
|
+
results.set(result.value.id, result.value.agent);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async terminate(agentId: string, reason?: string): Promise<void> {
|
|
166
|
+
const agent = this.pool.get(agentId);
|
|
167
|
+
if (!agent) {
|
|
168
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
agent.status = 'terminated';
|
|
172
|
+
|
|
173
|
+
// Remove from pool
|
|
174
|
+
this.pool.remove(agentId);
|
|
175
|
+
|
|
176
|
+
this.eventBus.emit(SystemEventTypes.AGENT_TERMINATED, {
|
|
177
|
+
agentId,
|
|
178
|
+
reason: reason ?? 'User requested',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async terminateAll(reason?: string): Promise<void> {
|
|
183
|
+
const agents = this.pool.getAll();
|
|
184
|
+
await Promise.allSettled(
|
|
185
|
+
agents.map(agent => this.terminate(agent.id, reason)),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async restart(agentId: string): Promise<IAgent> {
|
|
190
|
+
const agent = this.pool.get(agentId);
|
|
191
|
+
if (!agent) {
|
|
192
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const config = agent.config;
|
|
196
|
+
await this.terminate(agentId, 'Restart requested');
|
|
197
|
+
return this.spawn(config);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async updateConfig(agentId: string, config: Partial<IAgentConfig>): Promise<void> {
|
|
201
|
+
const agent = this.pool.get(agentId);
|
|
202
|
+
if (!agent) {
|
|
203
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Object.assign(agent.config, config);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getAgent(agentId: string): IAgent | undefined {
|
|
210
|
+
return this.pool.get(agentId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
getAllAgents(): IAgent[] {
|
|
214
|
+
return this.pool.getAll();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
getActiveCount(): number {
|
|
218
|
+
return this.pool.getByStatus('active').length +
|
|
219
|
+
this.pool.getByStatus('idle').length;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async checkHealth(agentId: string): Promise<IAgent['health']> {
|
|
223
|
+
const agent = this.pool.get(agentId);
|
|
224
|
+
if (!agent) {
|
|
225
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Simple health check based on metrics
|
|
229
|
+
const errorRate = agent.metrics
|
|
230
|
+
? agent.metrics.errorCount / Math.max(1, agent.metrics.tasksCompleted + agent.metrics.tasksFailed)
|
|
231
|
+
: 0;
|
|
232
|
+
|
|
233
|
+
const health: IAgent['health'] = {
|
|
234
|
+
status: errorRate > 0.5 ? 'unhealthy' : errorRate > 0.2 ? 'degraded' : 'healthy',
|
|
235
|
+
lastCheck: new Date(),
|
|
236
|
+
issues: [],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (errorRate > 0.2) {
|
|
240
|
+
health.issues?.push(`High error rate: ${(errorRate * 100).toFixed(1)}%`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
agent.health = health;
|
|
244
|
+
|
|
245
|
+
if (health.status !== 'healthy') {
|
|
246
|
+
this.eventBus.emit(SystemEventTypes.AGENT_HEALTH_CHANGED, {
|
|
247
|
+
agentId,
|
|
248
|
+
previousStatus: agent.status,
|
|
249
|
+
currentStatus: agent.status,
|
|
250
|
+
issues: health.issues,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return health;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get agent pool
|
|
259
|
+
*/
|
|
260
|
+
getPool(): IAgentPool {
|
|
261
|
+
return this.pool;
|
|
262
|
+
}
|
|
263
|
+
}
|