claudehq 1.0.3 → 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.
@@ -175,12 +175,13 @@ function loadEventsFromFile() {
175
175
  console.log(` Loaded ${claudeEvents.length} Claude events from: ${EVENTS_FILE}`);
176
176
 
177
177
  // Discover sessions from loaded events (process unique session IDs)
178
+ // Pass skipAutoUnhide: true to prevent unhiding sessions during initialization
178
179
  if (sessionUpdateCallback) {
179
180
  const sessionIds = new Set();
180
181
  for (const event of claudeEvents) {
181
182
  if (event.sessionId && !sessionIds.has(event.sessionId)) {
182
183
  sessionIds.add(event.sessionId);
183
- sessionUpdateCallback(event);
184
+ sessionUpdateCallback(event, { skipAutoUnhide: true });
184
185
  }
185
186
  }
186
187
  }
@@ -16,7 +16,6 @@ const TODOS_DIR = path.join(CLAUDE_DIR, 'todos');
16
16
  const PLANS_DIR = path.join(CLAUDE_DIR, 'plans');
17
17
  const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects');
18
18
  const EVENTS_DIR = path.join(CLAUDE_DIR, 'tasks-board');
19
- const ORCHESTRATIONS_DIR = path.join(CLAUDE_DIR, 'tasks-board', 'orchestrations');
20
19
 
21
20
  // Data files
22
21
  const CUSTOM_NAMES_FILE = path.join(TASKS_DIR, 'session-names.json');
@@ -36,7 +35,20 @@ const SESSION_STATUS = {
36
35
  OFFLINE: 'offline'
37
36
  };
38
37
 
39
- // Agent status constants (for orchestration)
38
+ // Timeouts and intervals
39
+ const HEALTH_CHECK_INTERVAL = 5000; // 5 seconds
40
+ const WORKING_TIMEOUT = 5 * 60 * 1000; // 5 minutes
41
+ const PERMISSION_POLL_INTERVAL = 1000; // 1 second
42
+ const EVENT_DEBOUNCE_DELAY = 100; // 100ms
43
+
44
+ // Limits
45
+ const MAX_EVENTS_IN_MEMORY = 1000;
46
+ const MAX_EVENTS_TO_BROADCAST = 500;
47
+
48
+ // Orchestration directories and status
49
+ const ORCHESTRATIONS_DIR = path.join(EVENTS_DIR, 'orchestrations');
50
+
51
+ // Agent status constants
40
52
  const AGENT_STATUS = {
41
53
  PENDING: 'pending',
42
54
  SPAWNING: 'spawning',
@@ -51,20 +63,17 @@ const AGENT_STATUS = {
51
63
  const ORCHESTRATION_STATUS = {
52
64
  DRAFT: 'draft',
53
65
  RUNNING: 'running',
54
- PAUSED: 'paused',
55
66
  COMPLETED: 'completed',
56
- FAILED: 'failed'
67
+ FAILED: 'failed',
68
+ PAUSED: 'paused'
57
69
  };
58
70
 
59
- // Timeouts and intervals
60
- const HEALTH_CHECK_INTERVAL = 5000; // 5 seconds
61
- const WORKING_TIMEOUT = 5 * 60 * 1000; // 5 minutes
62
- const PERMISSION_POLL_INTERVAL = 1000; // 1 second
63
- const EVENT_DEBOUNCE_DELAY = 100; // 100ms
64
-
65
- // Limits
66
- const MAX_EVENTS_IN_MEMORY = 1000;
67
- const MAX_EVENTS_TO_BROADCAST = 500;
71
+ // Spawner configuration
72
+ const SPAWNER_CONFIG = {
73
+ SESSION_PREFIX: 'tasks-board',
74
+ MAX_TRACKED_PROJECTS: 100,
75
+ MAX_AUTOCOMPLETE_RESULTS: 15
76
+ };
68
77
 
69
78
  module.exports = {
70
79
  // Directories
@@ -74,7 +83,6 @@ module.exports = {
74
83
  PLANS_DIR,
75
84
  PROJECTS_DIR,
76
85
  EVENTS_DIR,
77
- ORCHESTRATIONS_DIR,
78
86
 
79
87
  // Files
80
88
  CUSTOM_NAMES_FILE,
@@ -88,8 +96,6 @@ module.exports = {
88
96
 
89
97
  // Status
90
98
  SESSION_STATUS,
91
- AGENT_STATUS,
92
- ORCHESTRATION_STATUS,
93
99
 
94
100
  // Timing
95
101
  HEALTH_CHECK_INTERVAL,
@@ -99,5 +105,13 @@ module.exports = {
99
105
 
100
106
  // Limits
101
107
  MAX_EVENTS_IN_MEMORY,
102
- MAX_EVENTS_TO_BROADCAST
108
+ MAX_EVENTS_TO_BROADCAST,
109
+
110
+ // Orchestration
111
+ ORCHESTRATIONS_DIR,
112
+ AGENT_STATUS,
113
+ ORCHESTRATION_STATUS,
114
+
115
+ // Spawner
116
+ SPAWNER_CONFIG
103
117
  };
@@ -46,24 +46,6 @@ const EventTypes = {
46
46
  // Attention events
47
47
  ATTENTION_NEEDED: 'attention:needed',
48
48
  ATTENTION_CLEARED: 'attention:cleared',
49
-
50
- // Orchestration events
51
- ORCHESTRATION_CREATED: 'orchestration:created',
52
- ORCHESTRATION_UPDATED: 'orchestration:updated',
53
- ORCHESTRATION_STARTED: 'orchestration:started',
54
- ORCHESTRATION_PAUSED: 'orchestration:paused',
55
- ORCHESTRATION_COMPLETED: 'orchestration:completed',
56
- ORCHESTRATION_FAILED: 'orchestration:failed',
57
- ORCHESTRATION_DELETED: 'orchestration:deleted',
58
-
59
- // Agent events (within orchestrations)
60
- AGENT_CREATED: 'agent:created',
61
- AGENT_SPAWNED: 'agent:spawned',
62
- AGENT_STATUS_CHANGED: 'agent:status_changed',
63
- AGENT_OUTPUT: 'agent:output',
64
- AGENT_COMPLETED: 'agent:completed',
65
- AGENT_FAILED: 'agent:failed',
66
- AGENT_KILLED: 'agent:killed',
67
49
  };
68
50
 
69
51
  class EventBus {
package/lib/index.js CHANGED
@@ -25,13 +25,18 @@ const tasks = require('./data/tasks');
25
25
  const todos = require('./data/todos');
26
26
  const plans = require('./data/plans');
27
27
  const conversation = require('./data/conversation');
28
- const orchestrationData = require('./data/orchestration');
29
-
30
- // Orchestration
31
- const orchestrationExecutor = require('./orchestration/executor');
32
28
 
33
29
  // Routes
34
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');
35
40
 
36
41
  // =============================================================================
37
42
  // Module Wiring - Connect modules that need callbacks from other modules
@@ -174,14 +179,37 @@ const routes = createRoutes({
174
179
  updatePlan: plans.updatePlan,
175
180
  loadConversation: conversation.loadConversation,
176
181
 
177
- // Orchestration
178
- orchestrationData,
179
- orchestrationExecutor,
180
-
181
182
  // Utils
182
183
  sendToTmux: sessionManager.sendToTmux
183
184
  });
184
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
+
185
213
  // =============================================================================
186
214
  // HTTP Server
187
215
  // =============================================================================
@@ -330,101 +358,175 @@ const server = http.createServer((req, res) => {
330
358
  return routes.handleRenameSession(req, res, renameMatch[1]);
331
359
  }
332
360
 
333
- // Orchestration routes
334
- if (url.pathname === '/api/orchestrations' && req.method === 'GET') {
335
- return routes.handleGetOrchestrations(req, res);
361
+ // Task routes
362
+ const taskMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/(\d+)$/);
363
+ if (taskMatch && req.method === 'PATCH') {
364
+ return routes.handleUpdateTask(req, res, taskMatch[1], taskMatch[2]);
336
365
  }
337
- if (url.pathname === '/api/orchestrations' && req.method === 'POST') {
338
- return routes.handleCreateOrchestration(req, res);
366
+
367
+ const createMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
368
+ if (createMatch && req.method === 'POST') {
369
+ return routes.handleCreateTask(req, res, createMatch[1]);
339
370
  }
340
371
 
341
- const orchestrationMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)$/);
342
- if (orchestrationMatch && req.method === 'GET') {
343
- return routes.handleGetOrchestration(req, res, orchestrationMatch[1]);
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);
344
379
  }
345
- if (orchestrationMatch && req.method === 'PATCH') {
346
- return routes.handleUpdateOrchestration(req, res, orchestrationMatch[1]);
380
+ if (url.pathname === '/api/spawner/sessions' && req.method === 'GET') {
381
+ return spawnerRoutes.handleListSessions(req, res);
347
382
  }
348
- if (orchestrationMatch && req.method === 'DELETE') {
349
- return routes.handleDeleteOrchestration(req, res, orchestrationMatch[1]);
383
+ if (url.pathname === '/api/spawner/refresh' && req.method === 'POST') {
384
+ return spawnerRoutes.handleRefresh(req, res);
350
385
  }
351
386
 
352
- const orchestrationStartMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/start$/);
353
- if (orchestrationStartMatch && req.method === 'POST') {
354
- return routes.handleStartOrchestration(req, res, orchestrationStartMatch[1]);
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]);
355
396
  }
356
397
 
357
- const orchestrationStopMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/stop$/);
358
- if (orchestrationStopMatch && req.method === 'POST') {
359
- return routes.handleStopOrchestration(req, res, orchestrationStopMatch[1]);
398
+ const spawnerRestartMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/restart$/);
399
+ if (spawnerRestartMatch && req.method === 'POST') {
400
+ return spawnerRoutes.handleRestartSession(req, res, spawnerRestartMatch[1]);
360
401
  }
361
402
 
362
- // Agent routes
363
- const agentsMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents$/);
364
- if (agentsMatch && req.method === 'POST') {
365
- return routes.handleAddAgent(req, res, agentsMatch[1]);
403
+ const spawnerPromptMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/prompt$/);
404
+ if (spawnerPromptMatch && req.method === 'POST') {
405
+ return spawnerRoutes.handleSendPrompt(req, res, spawnerPromptMatch[1]);
366
406
  }
367
407
 
368
- const agentMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)$/);
369
- if (agentMatch && req.method === 'PATCH') {
370
- return routes.handleUpdateAgent(req, res, agentMatch[1], agentMatch[2]);
408
+ const spawnerCancelMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/cancel$/);
409
+ if (spawnerCancelMatch && req.method === 'POST') {
410
+ return spawnerRoutes.handleCancelSession(req, res, spawnerCancelMatch[1]);
371
411
  }
372
- if (agentMatch && req.method === 'DELETE') {
373
- return routes.handleRemoveAgent(req, res, agentMatch[1], agentMatch[2]);
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]);
374
416
  }
375
417
 
376
- const agentSpawnMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/spawn$/);
377
- if (agentSpawnMatch && req.method === 'POST') {
378
- return routes.handleSpawnAgent(req, res, agentSpawnMatch[1], agentSpawnMatch[2]);
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);
379
433
  }
380
434
 
381
- const agentKillMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/kill$/);
382
- if (agentKillMatch && req.method === 'POST') {
383
- return routes.handleKillAgent(req, res, agentKillMatch[1], agentKillMatch[2]);
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);
384
445
  }
385
446
 
386
- const agentPromptMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/prompt$/);
387
- if (agentPromptMatch && req.method === 'POST') {
388
- return routes.handleSendPromptToAgent(req, res, agentPromptMatch[1], agentPromptMatch[2]);
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]);
389
467
  }
390
468
 
391
- const agentStatusMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/status$/);
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$/);
392
489
  if (agentStatusMatch && req.method === 'GET') {
393
- return routes.handleGetAgentStatus(req, res, agentStatusMatch[1], agentStatusMatch[2]);
490
+ return orchestrationRoutes.handleGetAgentStatus(req, res, agentStatusMatch[1], agentStatusMatch[2]);
394
491
  }
395
492
 
396
- const agentDependencyMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/dependencies$/);
397
- if (agentDependencyMatch && req.method === 'POST') {
398
- return routes.handleAddAgentDependency(req, res, agentDependencyMatch[1], agentDependencyMatch[2]);
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]);
399
496
  }
400
497
 
401
- const agentDependencyRemoveMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/dependencies\/([^/]+)$/);
402
- if (agentDependencyRemoveMatch && req.method === 'DELETE') {
403
- return routes.handleRemoveAgentDependency(req, res, agentDependencyRemoveMatch[1], agentDependencyRemoveMatch[2], agentDependencyRemoveMatch[3]);
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]);
404
501
  }
405
502
 
406
- // Orchestration template routes
407
- if (url.pathname === '/api/orchestration-templates' && req.method === 'GET') {
408
- return routes.handleGetTemplates(req, res);
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]);
409
506
  }
410
507
 
411
- const templateMatch = url.pathname.match(/^\/api\/orchestration-templates\/([^/]+)$/);
412
- if (templateMatch && req.method === 'GET') {
413
- return routes.handleGetTemplate(req, res, templateMatch[1]);
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]);
414
512
  }
415
- if (templateMatch && req.method === 'POST') {
416
- return routes.handleCreateFromTemplate(req, res, templateMatch[1]);
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]);
417
517
  }
418
518
 
419
- // Task routes
420
- const taskMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/(\d+)$/);
421
- if (taskMatch && req.method === 'PATCH') {
422
- return routes.handleUpdateTask(req, res, taskMatch[1], taskMatch[2]);
519
+ // Templates
520
+ if (url.pathname === '/api/orchestration-templates' && req.method === 'GET') {
521
+ return orchestrationRoutes.handleListTemplates(req, res);
423
522
  }
424
523
 
425
- const createMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
426
- if (createMatch && req.method === 'POST') {
427
- return routes.handleCreateTask(req, res, createMatch[1]);
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]);
428
530
  }
429
531
 
430
532
  // Serve HTML
@@ -453,14 +555,11 @@ function start() {
453
555
  registerAllHandlers();
454
556
  console.log(` EventBus initialized with ${eventBus.getHandlerCount()} handlers`);
455
557
 
456
- // Initialize Claude events module
457
- claudeEvents.init();
458
-
459
- // Initialize session manager
558
+ // Initialize session manager (must be before claudeEvents so sessions are loaded before discovery)
460
559
  sessionManager.init();
461
560
 
462
- // Initialize orchestration executor
463
- orchestrationExecutor.init();
561
+ // Initialize Claude events module
562
+ claudeEvents.init();
464
563
 
465
564
  // Set up file watchers
466
565
  setupWatchers();
@@ -468,12 +567,20 @@ function start() {
468
567
  // Build plan session cache
469
568
  plans.buildPlanSessionCache();
470
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
+
471
578
  console.log(` Press Ctrl+C to stop\n`);
472
579
  });
473
580
  }
474
581
 
475
582
  // Export for testing
476
- module.exports = { server, start };
583
+ module.exports = { server, start, spawner };
477
584
 
478
585
  // Start if run directly
479
586
  if (require.main === module) {