claudehq 1.0.2 → 1.0.5
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/lib/core/claude-events.js +2 -1
- package/lib/core/config.js +39 -1
- package/lib/data/orchestration.js +941 -0
- package/lib/index.js +211 -23
- package/lib/orchestration/executor.js +635 -0
- package/lib/routes/orchestration.js +417 -0
- package/lib/routes/spawner.js +335 -0
- package/lib/sessions/manager.js +36 -9
- package/lib/spawner/index.js +51 -0
- package/lib/spawner/path-validator.js +366 -0
- package/lib/spawner/projects-manager.js +421 -0
- package/lib/spawner/session-spawner.js +1010 -0
- package/package.json +1 -1
- package/public/index.html +399 -18
- package/lib/server.js +0 -9364
package/lib/index.js
CHANGED
|
@@ -28,6 +28,15 @@ const conversation = require('./data/conversation');
|
|
|
28
28
|
|
|
29
29
|
// Routes
|
|
30
30
|
const { createRoutes } = require('./routes/api');
|
|
31
|
+
const { createSpawnerRoutes } = require('./routes/spawner');
|
|
32
|
+
const { createOrchestrationRoutes } = require('./routes/orchestration');
|
|
33
|
+
|
|
34
|
+
// Spawner
|
|
35
|
+
const { createSessionSpawner } = require('./spawner');
|
|
36
|
+
|
|
37
|
+
// Orchestration
|
|
38
|
+
const orchestrationData = require('./data/orchestration');
|
|
39
|
+
const orchestrationExecutor = require('./orchestration/executor');
|
|
31
40
|
|
|
32
41
|
// =============================================================================
|
|
33
42
|
// Module Wiring - Connect modules that need callbacks from other modules
|
|
@@ -108,15 +117,12 @@ function registerAllHandlers() {
|
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
// =============================================================================
|
|
111
|
-
// HTML Template - Loaded from
|
|
120
|
+
// HTML Template - Loaded from public/index.html
|
|
112
121
|
// =============================================================================
|
|
113
122
|
|
|
114
|
-
// For now, we'll read the HTML from the old server.js
|
|
115
|
-
// In future iterations, this should be moved to a separate file
|
|
116
123
|
let HTML = '';
|
|
117
124
|
|
|
118
125
|
function loadHTML() {
|
|
119
|
-
// Try to load from a separate HTML file first
|
|
120
126
|
const htmlPath = path.join(__dirname, '..', 'public', 'index.html');
|
|
121
127
|
if (fs.existsSync(htmlPath)) {
|
|
122
128
|
HTML = fs.readFileSync(htmlPath, 'utf-8');
|
|
@@ -124,21 +130,8 @@ function loadHTML() {
|
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
if (fs.existsSync(oldServerPath)) {
|
|
130
|
-
const content = fs.readFileSync(oldServerPath, 'utf-8');
|
|
131
|
-
const htmlStart = content.indexOf('const HTML = `<!DOCTYPE html>');
|
|
132
|
-
const htmlEnd = content.indexOf('</html>`;', htmlStart);
|
|
133
|
-
if (htmlStart !== -1 && htmlEnd !== -1) {
|
|
134
|
-
HTML = content.substring(htmlStart + 14, htmlEnd + 7);
|
|
135
|
-
console.log(` Extracted HTML template from legacy server.js`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Minimal fallback HTML
|
|
141
|
-
HTML = '<!DOCTYPE html><html><head><title>Claude Tasks Board</title></head><body><h1>Claude Tasks Board</h1><p>HTML template not found. Please check your installation.</p></body></html>';
|
|
133
|
+
// Fallback HTML if public/index.html is missing
|
|
134
|
+
HTML = '<!DOCTYPE html><html><head><title>Claude HQ</title></head><body><h1>Claude HQ</h1><p>HTML template not found. Please check your installation.</p></body></html>';
|
|
142
135
|
console.log(' Warning: Using minimal fallback HTML template');
|
|
143
136
|
}
|
|
144
137
|
|
|
@@ -190,6 +183,33 @@ const routes = createRoutes({
|
|
|
190
183
|
sendToTmux: sessionManager.sendToTmux
|
|
191
184
|
});
|
|
192
185
|
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// Session Spawner Setup
|
|
188
|
+
// =============================================================================
|
|
189
|
+
|
|
190
|
+
// Create session spawner instance
|
|
191
|
+
const spawner = createSessionSpawner({
|
|
192
|
+
dataDir: config.EVENTS_DIR
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Wire spawner to session manager broadcasts
|
|
196
|
+
spawner.setOnSessionUpdate((sessions) => {
|
|
197
|
+
broadcastUpdate('spawned_sessions', { sessions });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
spawner.setOnPermissionDetected((sessionId, permission) => {
|
|
201
|
+
broadcastUpdate('spawner_permission', { sessionId, permission });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Create spawner routes
|
|
205
|
+
const spawnerRoutes = createSpawnerRoutes({ spawner });
|
|
206
|
+
|
|
207
|
+
// Create orchestration routes
|
|
208
|
+
const orchestrationRoutes = createOrchestrationRoutes({
|
|
209
|
+
orchestrationData,
|
|
210
|
+
orchestrationExecutor
|
|
211
|
+
});
|
|
212
|
+
|
|
193
213
|
// =============================================================================
|
|
194
214
|
// HTTP Server
|
|
195
215
|
// =============================================================================
|
|
@@ -349,6 +369,166 @@ const server = http.createServer((req, res) => {
|
|
|
349
369
|
return routes.handleCreateTask(req, res, createMatch[1]);
|
|
350
370
|
}
|
|
351
371
|
|
|
372
|
+
// ==========================================================================
|
|
373
|
+
// Spawner Routes - New session spawning API
|
|
374
|
+
// ==========================================================================
|
|
375
|
+
|
|
376
|
+
// Sessions
|
|
377
|
+
if (url.pathname === '/api/spawner/sessions' && req.method === 'POST') {
|
|
378
|
+
return spawnerRoutes.handleSpawnSession(req, res);
|
|
379
|
+
}
|
|
380
|
+
if (url.pathname === '/api/spawner/sessions' && req.method === 'GET') {
|
|
381
|
+
return spawnerRoutes.handleListSessions(req, res);
|
|
382
|
+
}
|
|
383
|
+
if (url.pathname === '/api/spawner/refresh' && req.method === 'POST') {
|
|
384
|
+
return spawnerRoutes.handleRefresh(req, res);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const spawnerSessionMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)$/);
|
|
388
|
+
if (spawnerSessionMatch && req.method === 'GET') {
|
|
389
|
+
return spawnerRoutes.handleGetSession(req, res, spawnerSessionMatch[1]);
|
|
390
|
+
}
|
|
391
|
+
if (spawnerSessionMatch && req.method === 'PATCH') {
|
|
392
|
+
return spawnerRoutes.handleUpdateSession(req, res, spawnerSessionMatch[1]);
|
|
393
|
+
}
|
|
394
|
+
if (spawnerSessionMatch && req.method === 'DELETE') {
|
|
395
|
+
return spawnerRoutes.handleKillSession(req, res, spawnerSessionMatch[1]);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const spawnerRestartMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/restart$/);
|
|
399
|
+
if (spawnerRestartMatch && req.method === 'POST') {
|
|
400
|
+
return spawnerRoutes.handleRestartSession(req, res, spawnerRestartMatch[1]);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const spawnerPromptMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/prompt$/);
|
|
404
|
+
if (spawnerPromptMatch && req.method === 'POST') {
|
|
405
|
+
return spawnerRoutes.handleSendPrompt(req, res, spawnerPromptMatch[1]);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const spawnerCancelMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/cancel$/);
|
|
409
|
+
if (spawnerCancelMatch && req.method === 'POST') {
|
|
410
|
+
return spawnerRoutes.handleCancelSession(req, res, spawnerCancelMatch[1]);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const spawnerPermissionMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/permission$/);
|
|
414
|
+
if (spawnerPermissionMatch && req.method === 'POST') {
|
|
415
|
+
return spawnerRoutes.handlePermissionResponse(req, res, spawnerPermissionMatch[1]);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Projects
|
|
419
|
+
if (url.pathname === '/api/spawner/projects' && req.method === 'GET') {
|
|
420
|
+
return spawnerRoutes.handleListProjects(req, res, url);
|
|
421
|
+
}
|
|
422
|
+
if (url.pathname === '/api/spawner/projects' && req.method === 'POST') {
|
|
423
|
+
return spawnerRoutes.handleTrackProject(req, res);
|
|
424
|
+
}
|
|
425
|
+
if (url.pathname === '/api/spawner/projects' && req.method === 'DELETE') {
|
|
426
|
+
return spawnerRoutes.handleRemoveProject(req, res);
|
|
427
|
+
}
|
|
428
|
+
if (url.pathname === '/api/spawner/projects/autocomplete' && req.method === 'GET') {
|
|
429
|
+
return spawnerRoutes.handleAutocomplete(req, res, url);
|
|
430
|
+
}
|
|
431
|
+
if (url.pathname === '/api/spawner/projects/common' && req.method === 'GET') {
|
|
432
|
+
return spawnerRoutes.handleGetCommonDirectories(req, res);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ==========================================================================
|
|
436
|
+
// Orchestration Routes
|
|
437
|
+
// ==========================================================================
|
|
438
|
+
|
|
439
|
+
// Orchestrations CRUD
|
|
440
|
+
if (url.pathname === '/api/orchestrations' && req.method === 'GET') {
|
|
441
|
+
return orchestrationRoutes.handleListOrchestrations(req, res);
|
|
442
|
+
}
|
|
443
|
+
if (url.pathname === '/api/orchestrations' && req.method === 'POST') {
|
|
444
|
+
return orchestrationRoutes.handleCreateOrchestration(req, res);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const orchIdMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)$/);
|
|
448
|
+
if (orchIdMatch && req.method === 'GET') {
|
|
449
|
+
return orchestrationRoutes.handleGetOrchestration(req, res, orchIdMatch[1]);
|
|
450
|
+
}
|
|
451
|
+
if (orchIdMatch && req.method === 'PATCH') {
|
|
452
|
+
return orchestrationRoutes.handleUpdateOrchestration(req, res, orchIdMatch[1]);
|
|
453
|
+
}
|
|
454
|
+
if (orchIdMatch && req.method === 'DELETE') {
|
|
455
|
+
return orchestrationRoutes.handleDeleteOrchestration(req, res, orchIdMatch[1]);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Orchestration execution
|
|
459
|
+
const orchStartMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/start$/);
|
|
460
|
+
if (orchStartMatch && req.method === 'POST') {
|
|
461
|
+
return orchestrationRoutes.handleStartOrchestration(req, res, orchStartMatch[1]);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const orchStopMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/stop$/);
|
|
465
|
+
if (orchStopMatch && req.method === 'POST') {
|
|
466
|
+
return orchestrationRoutes.handleStopOrchestration(req, res, orchStopMatch[1]);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const orchStatsMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/stats$/);
|
|
470
|
+
if (orchStatsMatch && req.method === 'GET') {
|
|
471
|
+
return orchestrationRoutes.handleGetStats(req, res, orchStatsMatch[1]);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Agent routes
|
|
475
|
+
const agentAddMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents$/);
|
|
476
|
+
if (agentAddMatch && req.method === 'POST') {
|
|
477
|
+
return orchestrationRoutes.handleAddAgent(req, res, agentAddMatch[1]);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const agentIdMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)$/);
|
|
481
|
+
if (agentIdMatch && req.method === 'PATCH') {
|
|
482
|
+
return orchestrationRoutes.handleUpdateAgent(req, res, agentIdMatch[1], agentIdMatch[2]);
|
|
483
|
+
}
|
|
484
|
+
if (agentIdMatch && req.method === 'DELETE') {
|
|
485
|
+
return orchestrationRoutes.handleRemoveAgent(req, res, agentIdMatch[1], agentIdMatch[2]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const agentStatusMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/status$/);
|
|
489
|
+
if (agentStatusMatch && req.method === 'GET') {
|
|
490
|
+
return orchestrationRoutes.handleGetAgentStatus(req, res, agentStatusMatch[1], agentStatusMatch[2]);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const agentSpawnMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/spawn$/);
|
|
494
|
+
if (agentSpawnMatch && req.method === 'POST') {
|
|
495
|
+
return orchestrationRoutes.handleSpawnAgent(req, res, agentSpawnMatch[1], agentSpawnMatch[2]);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const agentKillMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/kill$/);
|
|
499
|
+
if (agentKillMatch && req.method === 'POST') {
|
|
500
|
+
return orchestrationRoutes.handleKillAgent(req, res, agentKillMatch[1], agentKillMatch[2]);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const agentPromptMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/prompt$/);
|
|
504
|
+
if (agentPromptMatch && req.method === 'POST') {
|
|
505
|
+
return orchestrationRoutes.handleSendAgentPrompt(req, res, agentPromptMatch[1], agentPromptMatch[2]);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Dependencies
|
|
509
|
+
const depAddMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/dependencies$/);
|
|
510
|
+
if (depAddMatch && req.method === 'POST') {
|
|
511
|
+
return orchestrationRoutes.handleAddDependency(req, res, depAddMatch[1], depAddMatch[2]);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const depRemoveMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/dependencies\/([a-z0-9-]+)$/);
|
|
515
|
+
if (depRemoveMatch && req.method === 'DELETE') {
|
|
516
|
+
return orchestrationRoutes.handleRemoveDependency(req, res, depRemoveMatch[1], depRemoveMatch[2], depRemoveMatch[3]);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Templates
|
|
520
|
+
if (url.pathname === '/api/orchestration-templates' && req.method === 'GET') {
|
|
521
|
+
return orchestrationRoutes.handleListTemplates(req, res);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const templateIdMatch = url.pathname.match(/^\/api\/orchestration-templates\/([a-z0-9-]+)$/);
|
|
525
|
+
if (templateIdMatch && req.method === 'GET') {
|
|
526
|
+
return orchestrationRoutes.handleGetTemplate(req, res, templateIdMatch[1]);
|
|
527
|
+
}
|
|
528
|
+
if (templateIdMatch && req.method === 'POST') {
|
|
529
|
+
return orchestrationRoutes.handleCreateFromTemplate(req, res, templateIdMatch[1]);
|
|
530
|
+
}
|
|
531
|
+
|
|
352
532
|
// Serve HTML
|
|
353
533
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
354
534
|
res.end(HTML);
|
|
@@ -375,24 +555,32 @@ function start() {
|
|
|
375
555
|
registerAllHandlers();
|
|
376
556
|
console.log(` EventBus initialized with ${eventBus.getHandlerCount()} handlers`);
|
|
377
557
|
|
|
558
|
+
// Initialize session manager (must be before claudeEvents so sessions are loaded before discovery)
|
|
559
|
+
sessionManager.init();
|
|
560
|
+
|
|
378
561
|
// Initialize Claude events module
|
|
379
562
|
claudeEvents.init();
|
|
380
563
|
|
|
381
|
-
// Initialize session manager
|
|
382
|
-
sessionManager.init();
|
|
383
|
-
|
|
384
564
|
// Set up file watchers
|
|
385
565
|
setupWatchers();
|
|
386
566
|
|
|
387
567
|
// Build plan session cache
|
|
388
568
|
plans.buildPlanSessionCache();
|
|
389
569
|
|
|
570
|
+
// Initialize session spawner
|
|
571
|
+
spawner.init();
|
|
572
|
+
console.log(` Session spawner initialized`);
|
|
573
|
+
|
|
574
|
+
// Initialize orchestration executor
|
|
575
|
+
orchestrationExecutor.init();
|
|
576
|
+
console.log(` Orchestration executor initialized`);
|
|
577
|
+
|
|
390
578
|
console.log(` Press Ctrl+C to stop\n`);
|
|
391
579
|
});
|
|
392
580
|
}
|
|
393
581
|
|
|
394
582
|
// Export for testing
|
|
395
|
-
module.exports = { server, start };
|
|
583
|
+
module.exports = { server, start, spawner };
|
|
396
584
|
|
|
397
585
|
// Start if run directly
|
|
398
586
|
if (require.main === module) {
|