@fleettools/server 0.1.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleettools/server",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "FleetTools server components for VPS deployment",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,11 @@
11
11
  "test": "bun test-api.ts",
12
12
  "lint": "echo 'No linter configured'"
13
13
  },
14
- "dependencies": {},
14
+ "dependencies": {
15
+ "@fleettools/squawk": "^0.2.0",
16
+ "@fleettools/events": "^0.2.0",
17
+ "@fleettools/db": "^0.2.0"
18
+ },
15
19
  "devDependencies": {
16
20
  "@types/node": "^20.10.6",
17
21
  "typescript": "^5.9.3"
package/tsconfig.json CHANGED
@@ -18,7 +18,13 @@
18
18
  "noUnusedParameters": false,
19
19
  "noImplicitReturns": true,
20
20
  "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": false
21
+ "noUncheckedIndexedAccess": false,
22
+ "baseUrl": ".",
23
+ "paths": {
24
+ "@fleettools/*": ["../../packages/*/src", "../../squawk/src"],
25
+ "@fleettools/db/*": ["../../packages/db/src/*"],
26
+ "@fleettools/events/*": ["../../packages/events/src/*"]
27
+ }
22
28
  },
23
29
  "include": [
24
30
  "src/**/*.ts"
@@ -1,148 +0,0 @@
1
- /**
2
- * Agent Lifecycle Management Methods
3
- *
4
- * Additional lifecycle management methods for AgentSpawner
5
- */
6
- // These are additional methods that can be added to the AgentSpawner class
7
- export class AgentLifecycleManager {
8
- agents = new Map();
9
- /**
10
- * Update agent heartbeat
11
- */
12
- async updateHeartbeat(agentId) {
13
- const agent = this.agents.get(agentId);
14
- if (!agent)
15
- return;
16
- agent.metadata = {
17
- ...agent.metadata,
18
- lastHeartbeat: new Date().toISOString()
19
- };
20
- agent.updatedAt = new Date().toISOString();
21
- this.agents.set(agentId, agent);
22
- }
23
- /**
24
- * Log agent error
25
- */
26
- async logError(agentId, error) {
27
- const agent = this.agents.get(agentId);
28
- if (!agent)
29
- return;
30
- const errors = agent.metadata?.errors || [];
31
- const errorEntry = {
32
- timestamp: new Date().toISOString(),
33
- error,
34
- count: 1
35
- };
36
- // Check if this error type already exists
37
- const existingError = errors.find((e) => e.error === error);
38
- if (existingError) {
39
- existingError.count++;
40
- existingError.timestamp = new Date().toISOString();
41
- }
42
- else {
43
- errors.push(errorEntry);
44
- }
45
- agent.metadata = {
46
- ...agent.metadata,
47
- errors: errors.slice(-10) // Keep last 10 errors
48
- };
49
- agent.updatedAt = new Date().toISOString();
50
- this.agents.set(agentId, agent);
51
- }
52
- /**
53
- * Check agent health status
54
- */
55
- async checkAgentHealth(agentId) {
56
- const agent = this.agents.get(agentId);
57
- if (!agent) {
58
- return { healthy: false, issues: ['Agent not found'] };
59
- }
60
- const issues = [];
61
- // Check if heartbeat is recent (within last 30 seconds)
62
- if (agent.metadata?.lastHeartbeat) {
63
- const lastHeartbeat = new Date(agent.metadata.lastHeartbeat).getTime();
64
- const now = Date.now();
65
- const heartbeatAge = (now - lastHeartbeat) / 1000;
66
- if (heartbeatAge > 30) {
67
- issues.push('Heartbeat timeout');
68
- }
69
- }
70
- else {
71
- issues.push('No heartbeat received');
72
- }
73
- }
74
- // Check error rate
75
- errors = agent.metadata?.errors || [];
76
- recentErrors = errors.filter((e) => {
77
- const errorTime = new Date(e.timestamp).getTime();
78
- const now = Date.now();
79
- const errorAge = (now - errorTime) / 1000;
80
- return errorAge < 300; // Last 5 minutes
81
- });
82
- if(recentErrors, length) { }
83
- }
84
- > 5;
85
- {
86
- issues.push('High error rate');
87
- }
88
- // Check uptime
89
- if (agent.createdAt) {
90
- const uptime = this.calculateUptime(agent.createdAt);
91
- if (uptime > 3600) { // More than 1 hour
92
- // Check if too long running without reset
93
- issues.push('Long uptime, consider restart');
94
- }
95
- }
96
- return {
97
- healthy: issues.length === 0,
98
- issues
99
- };
100
- /**
101
- * Restart agent
102
- */
103
- async;
104
- restartAgent(agentId, string);
105
- Promise < void > {
106
- const: agent = this.agents.get(agentId),
107
- if(, agent) {
108
- throw new Error(`Agent not found: ${agentId}`);
109
- },
110
- try: {
111
- // Terminate current agent
112
- if(agent) { }, : .pid
113
- }
114
- };
115
- {
116
- process.kill(agent.pid, 'SIGTERM');
117
- // Wait for graceful shutdown
118
- await new Promise(resolve => setTimeout(resolve, 5000));
119
- // Force kill if still running
120
- try {
121
- process.kill(agent.pid, 0);
122
- }
123
- catch {
124
- // Process already terminated
125
- }
126
- }
127
- // Spawn new agent with same configuration
128
- const spawnRequest = agent.metadata?.spawnRequest;
129
- if (spawnRequest) {
130
- // Update metadata for restart
131
- agent.metadata = {
132
- ...agent.metadata,
133
- restartCount: (agent.metadata?.restartCount || 0) + 1,
134
- lastRestart: new Date().toISOString()
135
- };
136
- console.log(`✓ Agent restarted: ${agentId} (attempt ${agent.metadata.restartCount})`);
137
- }
138
- try { }
139
- catch (error) {
140
- throw new Error(`Agent restart failed: ${error.message}`);
141
- }
142
- calculateUptime(createdAt, string);
143
- number;
144
- {
145
- const created = new Date(createdAt).getTime();
146
- const now = Date.now();
147
- return Math.floor((now - created) / 1000);
148
- }
@@ -1,460 +0,0 @@
1
- /**
2
- * Agent Spawner for FleetTools Coordination System
3
- *
4
- * Manages agent lifecycle: spawning, monitoring, and termination
5
- * Integrates with Squawk mailbox system for coordination
6
- */
7
- import { randomUUID } from 'node:crypto';
8
- import path from 'node:path';
9
- // Import types from coordination module (using local definition to avoid path issues)
10
- export var AgentType;
11
- (function (AgentType) {
12
- AgentType["FRONTEND"] = "frontend";
13
- AgentType["BACKEND"] = "backend";
14
- AgentType["TESTING"] = "testing";
15
- AgentType["DOCUMENTATION"] = "documentation";
16
- AgentType["SECURITY"] = "security";
17
- AgentType["PERFORMANCE"] = "performance";
18
- })(AgentType || (AgentType = {}));
19
- export var AgentStatus;
20
- (function (AgentStatus) {
21
- AgentStatus["SPAWNING"] = "spawning";
22
- AgentStatus["RUNNING"] = "running";
23
- AgentStatus["IDLE"] = "idle";
24
- AgentStatus["BUSY"] = "busy";
25
- AgentStatus["ERROR"] = "error";
26
- AgentStatus["TERMINATED"] = "terminated";
27
- AgentStatus["FAILED"] = "failed";
28
- })(AgentStatus || (AgentStatus = {}));
29
- export class AgentSpawner {
30
- agents = new Map();
31
- mailboxPath;
32
- constructor(mailboxPath) {
33
- this.mailboxPath = mailboxPath || path.join(process.cwd(), '.flightline', 'mailboxes');
34
- }
35
- /**
36
- * Spawn a new agent with timeout and retry logic
37
- */
38
- async spawn(request) {
39
- const agentId = `agt_${randomUUID()}`;
40
- const mailboxId = `mbx_${randomUUID()}`;
41
- const timeout = request.config?.timeout || 300000; // 5 minutes default
42
- const maxRetries = request.config?.retries || 3;
43
- const agent = {
44
- id: agentId,
45
- type: request.type,
46
- status: AgentStatus.SPAWNING,
47
- mailboxId,
48
- createdAt: new Date().toISOString(),
49
- updatedAt: new Date().toISOString(),
50
- metadata: {
51
- ...request.metadata,
52
- spawnRequest: request,
53
- spawnAttempts: 0,
54
- maxRetries
55
- }
56
- };
57
- let lastError = null;
58
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
59
- try {
60
- agent.metadata.spawnAttempts = attempt;
61
- agent.updatedAt = new Date().toISOString();
62
- // Create mailbox for agent
63
- await this.createMailbox(mailboxId, agentId);
64
- // Spawn agent process with timeout
65
- const pid = await this.executeAgentSpawnWithTimeout(agent, request, timeout);
66
- agent.pid = pid;
67
- agent.status = AgentStatus.RUNNING;
68
- agent.updatedAt = new Date().toISOString();
69
- // Store agent
70
- this.agents.set(agentId, agent);
71
- console.log(`✓ Agent spawned: ${agentId} (${request.type}) - attempt ${attempt}`);
72
- return agent;
73
- }
74
- catch (error) {
75
- lastError = error;
76
- agent.status = AgentStatus.FAILED;
77
- agent.updatedAt = new Date().toISOString();
78
- console.error(`✗ Agent spawn attempt ${attempt} failed:`, error.message);
79
- if (attempt < maxRetries) {
80
- // Wait before retry
81
- const retryDelay = Math.min(5000 * attempt, 15000); // Exponential backoff
82
- console.log(`Retrying agent spawn in ${retryDelay}ms...`);
83
- await new Promise(resolve => setTimeout(resolve, retryDelay));
84
- // Clean up failed attempt
85
- try {
86
- await this.cleanupFailedSpawn(agentId);
87
- }
88
- catch (cleanupError) {
89
- console.error(`Cleanup failed:`, cleanupError.message);
90
- }
91
- }
92
- }
93
- }
94
- // All attempts failed
95
- agent.status = AgentStatus.FAILED;
96
- agent.updatedAt = new Date().toISOString();
97
- agent.metadata.lastError = lastError?.message || 'Unknown error';
98
- this.agents.set(agentId, agent);
99
- console.error(`✗ Agent spawn failed after ${maxRetries} attempts: ${agentId}`);
100
- throw new Error(`Agent spawn failed after ${maxRetries} attempts: ${lastError?.message}`);
101
- }
102
- }
103
- ;
104
- try {
105
- // Create mailbox for agent
106
- await this.createMailbox(mailboxId, agentId);
107
- // Spawn the agent process
108
- const pid = await this.executeAgentSpawn(agent, request);
109
- agent.pid = pid;
110
- agent.status = AgentStatus.RUNNING;
111
- agent.updatedAt = new Date().toISOString();
112
- // Store agent
113
- this.agents.set(agentId, agent);
114
- console.log(`✓ Agent spawned: ${agentId} (${request.type})`);
115
- return agent;
116
- }
117
- catch (error) {
118
- agent.status = AgentStatus.FAILED;
119
- agent.updatedAt = new Date().toISOString();
120
- this.agents.set(agentId, agent);
121
- console.error(`✗ Failed to spawn agent ${agentId}:`, error.message);
122
- throw new Error(`Agent spawn failed: ${error.message}`);
123
- }
124
- /**
125
- * Monitor agent status and health with comprehensive checks
126
- */
127
- async;
128
- monitor(agentId, string);
129
- Promise < AgentMonitor > {
130
- const: agent = this.agents.get(agentId),
131
- if(, agent) {
132
- throw new Error(`Agent not found: ${agentId}`);
133
- },
134
- const: monitor, AgentMonitor = {
135
- status: agent.status,
136
- uptime: this.calculateUptime(agent.createdAt),
137
- lastHeartbeat: await this.getLastHeartbeat(agentId),
138
- resourceUsage: await this.getAgentResourceUsage(agentId),
139
- errors: await this.getAgentErrors(agentId)
140
- },
141
- // Check if process is still running
142
- if(agent) { }, : .pid
143
- };
144
- {
145
- try {
146
- process.kill(agent.pid, 0); // Signal 0 doesn't kill, just checks
147
- monitor.status = AgentStatus.RUNNING;
148
- // Update agent status if different
149
- if (agent.status !== AgentStatus.RUNNING) {
150
- agent.status = AgentStatus.RUNNING;
151
- agent.updatedAt = new Date().toISOString();
152
- this.agents.set(agentId, agent);
153
- }
154
- }
155
- catch {
156
- monitor.status = AgentStatus.TERMINATED;
157
- agent.status = AgentStatus.TERMINATED;
158
- agent.updatedAt = new Date().toISOString();
159
- this.agents.set(agentId, agent);
160
- }
161
- }
162
- // Health assessment
163
- if (monitor.errors && monitor.errors.length > 5) {
164
- monitor.status = AgentStatus.ERROR;
165
- agent.status = AgentStatus.ERROR;
166
- agent.updatedAt = new Date().toISOString();
167
- this.agents.set(agentId, agent);
168
- }
169
- return monitor;
170
- /**
171
- * Terminate an agent
172
- */
173
- async;
174
- terminate(agentId, string, graceful = true);
175
- Promise < void > {
176
- const: agent = this.agents.get(agentId),
177
- if(, agent) {
178
- throw new Error(`Agent not found: ${agentId}`);
179
- },
180
- console, : .log(`Terminating agent: ${agentId} (${agent.type})`),
181
- try: {
182
- if(agent) { }, : .pid
183
- }
184
- };
185
- {
186
- if (graceful) {
187
- // Send SIGTERM for graceful shutdown
188
- process.kill(agent.pid, 'SIGTERM');
189
- // Wait for graceful shutdown
190
- await new Promise(resolve => setTimeout(resolve, 5000));
191
- // Check if process is still running
192
- try {
193
- process.kill(agent.pid, 0);
194
- // Force kill if still running
195
- process.kill(agent.pid, 'SIGKILL');
196
- console.log(`⚠️ Force killed agent: ${agentId}`);
197
- }
198
- catch {
199
- // Process already terminated
200
- console.log(`✓ Agent terminated gracefully: ${agentId}`);
201
- }
202
- }
203
- else {
204
- // Force kill immediately
205
- process.kill(agent.pid, 'SIGKILL');
206
- console.log(`✓ Agent terminated: ${agentId}`);
207
- }
208
- }
209
- // Cleanup mailbox
210
- await this.cleanupMailbox(agent.mailboxId);
211
- // Update agent status
212
- agent.status = AgentStatus.TERMINATED;
213
- agent.updatedAt = new Date().toISOString();
214
- console.log(`✓ Agent cleanup complete: ${agentId}`);
215
- try { }
216
- catch (error) {
217
- agent.status = AgentStatus.ERROR;
218
- agent.updatedAt = new Date().toISOString();
219
- console.error(`✗ Error terminating agent ${agentId}:`, error.message);
220
- throw new Error(`Agent termination failed: ${error.message}`);
221
- }
222
- /**
223
- * Get all active agents
224
- */
225
- getActiveAgents();
226
- AgentHandle[];
227
- {
228
- return Array.from(this.agents.values()).filter(agent => agent.status === AgentStatus.RUNNING ||
229
- agent.status === AgentStatus.IDLE ||
230
- agent.status === AgentStatus.BUSY);
231
- }
232
- /**
233
- * Get agents by type
234
- */
235
- getAgentsByType(type, AgentType);
236
- AgentHandle[];
237
- {
238
- return Array.from(this.agents.values()).filter(agent => agent.type === type);
239
- }
240
- /**
241
- * Get agent by ID
242
- */
243
- getAgent(agentId, string);
244
- AgentHandle | undefined;
245
- {
246
- return this.agents.get(agentId);
247
- }
248
- async;
249
- createMailbox(mailboxId, string, agentId, string);
250
- Promise < void > {
251
- const: mailboxDir = path.join(this.mailboxPath, mailboxId),
252
- // Create mailbox directory
253
- await, this: .ensureDirectory(mailboxDir),
254
- // Create mailbox manifest
255
- const: manifest = {
256
- id: mailboxId,
257
- agentId,
258
- createdAt: new Date().toISOString(),
259
- type: 'agent-mailbox'
260
- },
261
- const: manifestPath = path.join(mailboxDir, 'manifest.json'),
262
- await, this: .writeFile(manifestPath, JSON.stringify(manifest, null, 2))
263
- };
264
- async;
265
- cleanupMailbox(mailboxId, string);
266
- Promise < void > {
267
- const: mailboxDir = path.join(this.mailboxPath, mailboxId),
268
- try: {
269
- await, this: .removeDirectory(mailboxDir),
270
- console, : .log(`✓ Cleaned up mailbox: ${mailboxId}`)
271
- }, catch(error) {
272
- console.error(`⚠️ Failed to cleanup mailbox ${mailboxId}:`, error.message);
273
- }
274
- };
275
- async;
276
- executeAgentSpawn(agent, AgentHandle, request, AgentSpawnRequest);
277
- Promise < number > {
278
- // This is a placeholder implementation
279
- // In a real system, this would spawn the actual agent process
280
- // For now, we'll simulate with a child process
281
- const: { spawn } = await import('node:child_process'),
282
- return: new Promise((resolve, reject) => {
283
- const args = [
284
- '--agent-id', agent.id,
285
- '--agent-type', agent.type,
286
- '--mailbox-id', agent.mailboxId,
287
- '--task', request.task || ''
288
- ];
289
- if (request.config?.timeout) {
290
- args.push('--timeout', request.config.timeout.toString());
291
- }
292
- const childProcess = spawn('node', ['src/agent-runner.js', ...args], {
293
- stdio: ['pipe', 'pipe', 'pipe'],
294
- detached: false
295
- });
296
- childProcess.on('spawn', () => {
297
- console.log(`Agent process spawned with PID: ${childProcess.pid}`);
298
- resolve(childProcess.pid);
299
- });
300
- childProcess.on('error', (error) => {
301
- console.error(`Failed to spawn agent process:`, error);
302
- reject(error);
303
- });
304
- // Handle agent output
305
- if (childProcess.stdout) {
306
- childProcess.stdout.on('data', (data) => {
307
- console.log(`[${agent.id}] ${data.toString().trim()}`);
308
- });
309
- }
310
- if (childProcess.stderr) {
311
- childProcess.stderr.on('data', (data) => {
312
- console.error(`[${agent.id}] ERROR: ${data.toString().trim()}`);
313
- });
314
- }
315
- childProcess.on('close', (code) => {
316
- if (code !== 0) {
317
- console.log(`Agent ${agent.id} exited with code: ${code}`);
318
- }
319
- });
320
- })
321
- };
322
- async;
323
- executeAgentSpawnWithTimeout(agent, AgentHandle, request, AgentSpawnRequest, timeout, number);
324
- Promise < number > {
325
- return: Promise.race([
326
- this.executeAgentSpawn(agent, request),
327
- new Promise((_, reject) => setTimeout(() => reject(new Error('Agent spawn timeout')), timeout))
328
- ])
329
- };
330
- async;
331
- cleanupFailedSpawn(agentId, string);
332
- Promise < void > {
333
- try: {
334
- // Terminate any existing process
335
- const: agent = this.agents.get(agentId),
336
- if(agent, pid) {
337
- try {
338
- process.kill(agent.pid, 'SIGKILL');
339
- console.log(`✓ Cleaned up process ${agent.pid}`);
340
- }
341
- catch {
342
- // Process already terminated
343
- }
344
- }
345
- // Cleanup mailbox
346
- ,
347
- // Cleanup mailbox
348
- const: agentHandle = this.agents.get(agentId),
349
- if(agentHandle, mailboxId) {
350
- await this.cleanupMailbox(agentHandle.mailboxId);
351
- }
352
- }, catch(error) {
353
- console.error(`Cleanup error for ${agentId}:`, error.message);
354
- }
355
- };
356
- async;
357
- getLastHeartbeat(agentId, string);
358
- Promise < string | undefined > {
359
- // Placeholder: In real implementation, read from Squawk mailbox
360
- return: new Date().toISOString()
361
- };
362
- async;
363
- getAgentErrors(agentId, string);
364
- Promise < Array < { timestamp: string, error: string, count: number } >> {
365
- // Placeholder: In real implementation, read from error logs or database
366
- const: agent = this.agents.get(agentId),
367
- if(agent, metadata, errors) {
368
- return agent.metadata.errors;
369
- },
370
- return: []
371
- };
372
- async;
373
- getAgentResourceUsage(agentId, string);
374
- Promise < { memory: number, cpu: number } | undefined > {
375
- // Placeholder: In real implementation, get from process monitoring
376
- const: agent = this.agents.get(agentId),
377
- if(agent, pid) {
378
- try {
379
- // This would use system monitoring APIs in a real implementation
380
- // For now, return simulated values
381
- return {
382
- memory: Math.floor(Math.random() * 500) + 100, // MB
383
- cpu: Math.floor(Math.random() * 80) + 10 // percentage
384
- };
385
- }
386
- catch {
387
- return undefined;
388
- }
389
- },
390
- return: undefined
391
- };
392
- /**
393
- * Update agent heartbeat
394
- */
395
- async;
396
- updateHeartbeat(agentId, string);
397
- Promise < void > {
398
- const: agent = this.agents.get(agentId),
399
- if(, agent) { }, return: ,
400
- agent, : .metadata = {
401
- ...agent.metadata,
402
- lastHeartbeat: new Date().toISOString()
403
- },
404
- agent, : .updatedAt = new Date().toISOString(),
405
- this: .agents.set(agentId, agent)
406
- };
407
- /**
408
- * Log agent error
409
- */
410
- async;
411
- logError(agentId, string, error, string);
412
- Promise < void > {
413
- const: agent = this.agents.get(agentId),
414
- if(, agent) { }, return: ,
415
- const: errors = agent.metadata?.errors || [],
416
- const: errorEntry = {
417
- timestamp: new Date().toISOString(),
418
- error,
419
- count: 1
420
- },
421
- // Check if this error type already exists
422
- const: existingError = errors.find(e => e.error === error),
423
- if(existingError) {
424
- existingError.count++;
425
- existingError.timestamp = new Date().toISOString();
426
- }, else: {
427
- errors, : .push(errorEntry)
428
- },
429
- agent, : .metadata = {
430
- ...agent.metadata,
431
- errors: errors.slice(-10) // Keep last 10 errors
432
- },
433
- agent, : .updatedAt = new Date().toISOString(),
434
- this: .agents.set(agentId, agent)
435
- };
436
- calculateUptime(createdAt, string);
437
- number;
438
- {
439
- const created = new Date(createdAt).getTime();
440
- const now = Date.now();
441
- return Math.floor((now - created) / 1000);
442
- }
443
- async;
444
- ensureDirectory(dirPath, string);
445
- Promise < void > {
446
- const: { mkdir } = await import('node:fs/promises'),
447
- await
448
- };
449
- async;
450
- writeFile(filePath, string, content, string);
451
- Promise < void > {
452
- const: { writeFile } = await import('node:fs/promises'),
453
- await
454
- };
455
- async;
456
- removeDirectory(dirPath, string);
457
- Promise < void > {
458
- const: { rm } = await import('node:fs/promises'),
459
- await
460
- };