@luckydraw/cumulus 0.27.18 → 0.27.19

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.
Files changed (58) hide show
  1. package/dist/cli/cumulus.js +0 -0
  2. package/dist/gateway/adapters/webchat.d.ts +1 -3
  3. package/dist/gateway/adapters/webchat.d.ts.map +1 -1
  4. package/dist/gateway/adapters/webchat.js +7 -150
  5. package/dist/gateway/adapters/webchat.js.map +1 -1
  6. package/dist/gateway/config.d.ts +7 -0
  7. package/dist/gateway/config.d.ts.map +1 -1
  8. package/dist/gateway/config.js +13 -0
  9. package/dist/gateway/config.js.map +1 -1
  10. package/dist/gateway/daemon.js +26 -8
  11. package/dist/gateway/daemon.js.map +1 -1
  12. package/dist/gateway/gateway-agents-mcp.js +49 -0
  13. package/dist/gateway/gateway-agents-mcp.js.map +1 -1
  14. package/dist/gateway/push.d.ts +66 -0
  15. package/dist/gateway/push.d.ts.map +1 -0
  16. package/dist/gateway/push.js +131 -0
  17. package/dist/gateway/push.js.map +1 -0
  18. package/dist/gateway/server.d.ts +3 -9
  19. package/dist/gateway/server.d.ts.map +1 -1
  20. package/dist/gateway/server.js +112 -243
  21. package/dist/gateway/server.js.map +1 -1
  22. package/dist/gateway/static/sw.js +73 -0
  23. package/dist/gateway/static/widget.js +126 -755
  24. package/dist/index.d.ts +3 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/lib/config.d.ts +0 -8
  29. package/dist/lib/config.d.ts.map +1 -1
  30. package/dist/lib/config.js +1 -61
  31. package/dist/lib/config.js.map +1 -1
  32. package/dist/lib/content-detector.d.ts.map +1 -1
  33. package/dist/lib/content-detector.js +2 -9
  34. package/dist/lib/content-detector.js.map +1 -1
  35. package/dist/lib/gateway.d.ts +2 -14
  36. package/dist/lib/gateway.d.ts.map +1 -1
  37. package/dist/lib/gateway.js +103 -234
  38. package/dist/lib/gateway.js.map +1 -1
  39. package/dist/lib/summaries.d.ts.map +1 -1
  40. package/dist/lib/summaries.js +2 -9
  41. package/dist/lib/summaries.js.map +1 -1
  42. package/dist/mcp/index.js +0 -0
  43. package/dist/mcp/tool-handler.d.ts.map +1 -1
  44. package/dist/mcp/tool-handler.js +3 -10
  45. package/dist/mcp/tool-handler.js.map +1 -1
  46. package/dist/tui/hooks/useClaudeProcess.d.ts.map +1 -1
  47. package/dist/tui/hooks/useClaudeProcess.js +23 -18
  48. package/dist/tui/hooks/useClaudeProcess.js.map +1 -1
  49. package/package.json +3 -1
  50. package/dist/gateway/device-mcp.d.ts +0 -21
  51. package/dist/gateway/device-mcp.d.ts.map +0 -1
  52. package/dist/gateway/device-mcp.js +0 -165
  53. package/dist/gateway/device-mcp.js.map +0 -1
  54. package/dist/lib/executor.d.ts +0 -168
  55. package/dist/lib/executor.d.ts.map +0 -1
  56. package/dist/lib/executor.js +0 -284
  57. package/dist/lib/executor.js.map +0 -1
  58. package/dist/lib/projects.d.ts.map +0 -1
@@ -17,15 +17,14 @@ import * as fs from 'fs';
17
17
  import * as http from 'http';
18
18
  import * as os from 'os';
19
19
  import * as path from 'path';
20
- import { WebSocketServer } from 'ws';
21
20
  import { loadThreadConfig, saveThreadConfig } from '../lib/config.js';
22
21
  import { loadEmbeddings, generateMissingEmbeddings } from '../lib/embeddings.js';
23
- import { ExecutorRegistry } from '../lib/executor.js';
24
22
  import { sendMessage, getOrCreateThread, clearThreadCache, } from '../lib/gateway.js';
25
23
  import { catchUpSegmentation, loadSegmentBoundaries, saveSegmentBoundaries, } from '../lib/segments.js';
26
24
  import { listTemplates, scaffoldFromTemplate } from '../lib/templates.js';
27
25
  import { serveStaticFile } from './adapters/webchat.js';
28
26
  import { authenticate } from './auth.js';
27
+ import { configureVapid, sendNotification, subscribe as pushSubscribe, unsubscribe as pushUnsubscribe, } from './push.js';
29
28
  const threadQueues = new Map();
30
29
  const threadBusy = new Map();
31
30
  function enqueue(threadName, item) {
@@ -86,7 +85,6 @@ async function processMessage(threadName, item, opts) {
86
85
  projectRoot: opts.projectRoot,
87
86
  extraMcpServers: opts.extraMcpServers,
88
87
  gatewayAgentsConfig: opts.gatewayAgentsConfig,
89
- executorRegistry: opts.executorRegistry,
90
88
  signal: ac.signal,
91
89
  onToken: text => sendSSE('token', { text }),
92
90
  onSegment: segment => sendSSE('segment', segment),
@@ -204,7 +202,6 @@ async function handleAgentInject(req, res, pipelineOpts) {
204
202
  await getOrCreateThread(targetName);
205
203
  // Send as a message through the pipeline (fire-and-forget)
206
204
  threadBusy.set(targetName, true);
207
- const broadcast = pipelineOpts.broadcastToThread;
208
205
  sendMessage({
209
206
  threadName: targetName,
210
207
  message: formatted,
@@ -214,35 +211,6 @@ async function handleAgentInject(req, res, pipelineOpts) {
214
211
  projectRoot: pipelineOpts.projectRoot,
215
212
  extraMcpServers: pipelineOpts.extraMcpServers,
216
213
  gatewayAgentsConfig: pipelineOpts.gatewayAgentsConfig,
217
- executorRegistry: pipelineOpts.executorRegistry,
218
- })
219
- .then(result => {
220
- // Notify connected clients that new messages arrived on this thread
221
- console.log(`[Gateway] Agent inject completed for "${targetName}", broadcast=${!!broadcast}, result=${!!result}`);
222
- if (broadcast && result) {
223
- const newMessages = [];
224
- if (result.userMessage) {
225
- newMessages.push({ id: result.userMessage.id, role: 'user', content: formatted });
226
- }
227
- if (result.assistantMessage) {
228
- newMessages.push({
229
- id: result.assistantMessage.id,
230
- role: 'assistant',
231
- content: result.response,
232
- });
233
- }
234
- if (newMessages.length > 0) {
235
- console.log(`[Gateway] Broadcasting new_messages to "${targetName}": ${newMessages.length} messages`);
236
- broadcast(targetName, {
237
- type: 'new_messages',
238
- threadName: targetName,
239
- messages: newMessages,
240
- });
241
- }
242
- }
243
- else if (!broadcast) {
244
- console.warn(`[Gateway] No broadcast function — agent inject for "${targetName}" won't notify clients`);
245
- }
246
214
  })
247
215
  .catch(err => console.error(`[Gateway] agent inject error for ${targetName}:`, err))
248
216
  .finally(() => {
@@ -354,7 +322,6 @@ function drainAgentQueue(threadName, pipelineOpts) {
354
322
  projectRoot: pipelineOpts.projectRoot,
355
323
  extraMcpServers: pipelineOpts.extraMcpServers,
356
324
  gatewayAgentsConfig: pipelineOpts.gatewayAgentsConfig,
357
- executorRegistry: pipelineOpts.executorRegistry,
358
325
  })
359
326
  .catch(err => console.error(`[Gateway] agent queue drain error for ${threadName}:`, err))
360
327
  .finally(() => {
@@ -382,15 +349,6 @@ function parseRoute(url) {
382
349
  query,
383
350
  };
384
351
  }
385
- // Match /api/device/:executorId/tool
386
- const deviceMatch = pathPart.match(/^\/api\/device\/([^/]+)\/tool$/);
387
- if (deviceMatch) {
388
- return {
389
- path: '/api/device/:executorId/tool',
390
- params: { executorId: decodeURIComponent(deviceMatch[1]) },
391
- query,
392
- };
393
- }
394
352
  return { path: pathPart, params: {}, query };
395
353
  }
396
354
  function readBody(req) {
@@ -426,27 +384,16 @@ async function handlePostMessage(threadName, req, res, opts) {
426
384
  drainQueue(threadName, opts).catch(err => console.error(`[Gateway] drain error:`, err));
427
385
  }
428
386
  async function handleGetHistory(threadName, query, res) {
429
- const limit = Math.min(parseInt(query.limit || '50', 10) || 50, 500);
430
- const hasOffset = query.offset !== undefined;
387
+ const limit = Math.min(parseInt(query.limit || '20', 10) || 20, 200);
431
388
  const offset = parseInt(query.offset || '0', 10) || 0;
432
389
  try {
433
390
  const thread = await getOrCreateThread(threadName);
434
391
  const all = await thread.history.getAll();
435
392
  const total = all.length;
436
- // If no explicit offset, return the MOST RECENT messages (tail of history)
437
- let slice;
438
- let startIndex;
439
- if (!hasOffset) {
440
- startIndex = Math.max(0, total - limit);
441
- slice = all.slice(startIndex);
442
- }
443
- else {
444
- startIndex = offset;
445
- slice = all.slice(offset, offset + limit);
446
- }
393
+ const slice = all.slice(offset, offset + limit);
447
394
  const messages = slice.map((m, i) => ({
448
395
  id: m.id,
449
- index: startIndex + i,
396
+ index: offset + i,
450
397
  role: m.role,
451
398
  content: m.content,
452
399
  timestamp: m.timestamp,
@@ -477,7 +424,7 @@ async function handleGetStatus(threadName, res) {
477
424
  jsonResponse(res, 500, { error: String(err) });
478
425
  }
479
426
  }
480
- async function handleListThreads(res, registry) {
427
+ async function handleListThreads(res) {
481
428
  try {
482
429
  if (!fs.existsSync(THREADS_DIR)) {
483
430
  jsonResponse(res, 200, { threads: [] });
@@ -498,7 +445,7 @@ async function handleListThreads(res, registry) {
498
445
  catch {
499
446
  // skip
500
447
  }
501
- // Read thread config to include projectDir and executor info
448
+ // Read thread config to include projectDir if set
502
449
  const tc = await loadThreadConfig(name);
503
450
  const entry = {
504
451
  name,
@@ -508,12 +455,6 @@ async function handleListThreads(res, registry) {
508
455
  if (tc.projectDir) {
509
456
  entry.projectDir = tc.projectDir;
510
457
  }
511
- // Include executor info
512
- const executor = tc.executor || 'thundercat';
513
- entry.executor = executor;
514
- if (registry) {
515
- entry.executorOnline = registry.isOnline(executor);
516
- }
517
458
  threads.push(entry);
518
459
  }
519
460
  // Sort by last activity descending
@@ -530,46 +471,6 @@ async function handleListThreads(res, registry) {
530
471
  jsonResponse(res, 500, { error: String(err) });
531
472
  }
532
473
  }
533
- async function handleListExecutors(res, registry) {
534
- try {
535
- const executors = registry.listExecutors();
536
- jsonResponse(res, 200, { executors });
537
- }
538
- catch (err) {
539
- jsonResponse(res, 500, { error: String(err) });
540
- }
541
- }
542
- /**
543
- * Route a device tool call to a remote executor.
544
- * The executor executes the tool locally and returns the result.
545
- */
546
- async function handleDeviceToolCall(executorId, req, res, registry) {
547
- let body;
548
- try {
549
- const raw = await readBody(req);
550
- body = JSON.parse(raw);
551
- }
552
- catch {
553
- jsonResponse(res, 400, { error: 'Invalid JSON body' });
554
- return;
555
- }
556
- if (!body.tool) {
557
- jsonResponse(res, 400, { error: 'Missing tool name' });
558
- return;
559
- }
560
- if (!registry.isOnline(executorId)) {
561
- jsonResponse(res, 503, { error: `Device "${executorId}" is offline` });
562
- return;
563
- }
564
- try {
565
- const result = await registry.deviceToolCall(executorId, body.tool, body.args || {});
566
- jsonResponse(res, 200, result);
567
- }
568
- catch (err) {
569
- const errorMsg = err instanceof Error ? err.message : String(err);
570
- jsonResponse(res, 500, { error: errorMsg, isError: true });
571
- }
572
- }
573
474
  async function handleDeleteThread(threadName, req, res) {
574
475
  const confirm = req.headers['x-confirm'];
575
476
  if (confirm !== 'delete') {
@@ -618,60 +519,6 @@ async function handleDeleteThread(threadName, req, res) {
618
519
  jsonResponse(res, 500, { error: String(err) });
619
520
  }
620
521
  }
621
- async function handleDeleteMessages(threadName, req, res) {
622
- const confirm = req.headers['x-confirm'];
623
- if (confirm !== 'delete') {
624
- jsonResponse(res, 400, { error: 'Missing X-Confirm: delete header' });
625
- return;
626
- }
627
- let body;
628
- try {
629
- const raw = await readBody(req);
630
- body = JSON.parse(raw);
631
- }
632
- catch {
633
- jsonResponse(res, 400, { error: 'Invalid JSON body' });
634
- return;
635
- }
636
- if (!Array.isArray(body.messageIds) || body.messageIds.length === 0) {
637
- jsonResponse(res, 400, { error: 'Missing or empty "messageIds" array' });
638
- return;
639
- }
640
- const idsToDelete = new Set(body.messageIds);
641
- try {
642
- const filePath = path.join(THREADS_DIR, threadName + '.jsonl');
643
- if (!fs.existsSync(filePath)) {
644
- jsonResponse(res, 404, { error: 'Thread not found' });
645
- return;
646
- }
647
- const raw = fs.readFileSync(filePath, 'utf-8');
648
- const lines = raw.split('\n').filter(l => l.trim());
649
- const kept = [];
650
- let deleted = 0;
651
- for (const line of lines) {
652
- try {
653
- const msg = JSON.parse(line);
654
- if (msg.id && idsToDelete.has(msg.id)) {
655
- deleted++;
656
- }
657
- else {
658
- kept.push(line);
659
- }
660
- }
661
- catch {
662
- // Malformed line — keep it
663
- kept.push(line);
664
- }
665
- }
666
- fs.writeFileSync(filePath, kept.join('\n') + (kept.length > 0 ? '\n' : ''), 'utf-8');
667
- // Clear in-memory cache so next request reads fresh data
668
- clearThreadCache(threadName);
669
- jsonResponse(res, 200, { deleted, remaining: kept.length });
670
- }
671
- catch (err) {
672
- jsonResponse(res, 500, { error: String(err) });
673
- }
674
- }
675
522
  async function handleSyncMessages(threadName, req, res) {
676
523
  let body;
677
524
  try {
@@ -759,14 +606,6 @@ async function handleUpdateThreadConfig(threadName, req, res) {
759
606
  updates.projectDir = body.projectDir;
760
607
  if (body.template !== undefined)
761
608
  updates.template = body.template;
762
- if (body.executor !== undefined)
763
- updates.executor = body.executor;
764
- if (body.executorPaths !== undefined)
765
- updates.executorPaths = body.executorPaths;
766
- if (body.devices !== undefined)
767
- updates.devices = body.devices;
768
- if (body.gitRemoteUrl !== undefined)
769
- updates.gitRemoteUrl = body.gitRemoteUrl;
770
609
  await saveThreadConfig(threadName, updates);
771
610
  const updated = await loadThreadConfig(threadName);
772
611
  jsonResponse(res, 200, { config: updated });
@@ -917,15 +756,95 @@ async function handleMigrate(req, res, opts) {
917
756
  jsonResponse(res, 500, { error: String(err) });
918
757
  }
919
758
  }
759
+ // ─── Push notification handlers ─────────────────────────────────────────────
760
+ function handleGetVapidKey(res, vapid) {
761
+ if (!vapid) {
762
+ jsonResponse(res, 404, { error: 'Push notifications not configured' });
763
+ return;
764
+ }
765
+ jsonResponse(res, 200, { publicKey: vapid.publicKey });
766
+ }
767
+ async function handlePushSubscribe(req, res) {
768
+ let body;
769
+ try {
770
+ const raw = await readBody(req);
771
+ body = JSON.parse(raw);
772
+ }
773
+ catch {
774
+ jsonResponse(res, 400, { error: 'Invalid JSON body' });
775
+ return;
776
+ }
777
+ if (!body.endpoint || !body.keys?.p256dh || !body.keys?.auth) {
778
+ jsonResponse(res, 400, {
779
+ error: 'Missing required fields: endpoint, keys.p256dh, keys.auth',
780
+ });
781
+ return;
782
+ }
783
+ const sub = {
784
+ endpoint: body.endpoint,
785
+ keys: { p256dh: body.keys.p256dh, auth: body.keys.auth },
786
+ createdAt: new Date().toISOString(),
787
+ label: body.label,
788
+ };
789
+ pushSubscribe(sub);
790
+ jsonResponse(res, 200, { subscribed: true });
791
+ }
792
+ async function handlePushUnsubscribe(req, res) {
793
+ let body;
794
+ try {
795
+ const raw = await readBody(req);
796
+ body = JSON.parse(raw);
797
+ }
798
+ catch {
799
+ jsonResponse(res, 400, { error: 'Invalid JSON body' });
800
+ return;
801
+ }
802
+ if (!body.endpoint) {
803
+ jsonResponse(res, 400, { error: 'Missing "endpoint"' });
804
+ return;
805
+ }
806
+ const removed = pushUnsubscribe(body.endpoint);
807
+ jsonResponse(res, 200, { removed });
808
+ }
809
+ async function handleNotify(req, res) {
810
+ let body;
811
+ try {
812
+ const raw = await readBody(req);
813
+ body = JSON.parse(raw);
814
+ }
815
+ catch {
816
+ jsonResponse(res, 400, { error: 'Invalid JSON body' });
817
+ return;
818
+ }
819
+ const title = body.title || 'Cumulus';
820
+ const notifBody = body.body || body.message || '';
821
+ if (!notifBody) {
822
+ jsonResponse(res, 400, { error: 'Missing "body" or "message"' });
823
+ return;
824
+ }
825
+ try {
826
+ const delivered = await sendNotification({
827
+ title,
828
+ body: notifBody,
829
+ threadName: body.threadName,
830
+ priority: body.priority,
831
+ });
832
+ jsonResponse(res, 200, { delivered });
833
+ }
834
+ catch (err) {
835
+ jsonResponse(res, 500, { error: String(err) });
836
+ }
837
+ }
920
838
  // ─── Server ──────────────────────────────────────────────────────────────────
921
839
  export async function startGatewayServer(options) {
922
840
  const { port, ...pipelineOpts } = options;
923
841
  let currentApiKeys = options.apiKeys;
924
- // Create or use provided executor registry
925
- const executorRegistry = options.executorRegistry || new ExecutorRegistry();
926
- pipelineOpts.executorRegistry = executorRegistry;
927
- // Set up executor WebSocket server
928
- const executorWss = new WebSocketServer({ noServer: true });
842
+ // Initialize VAPID for push notifications (if configured)
843
+ const vapidConfig = options.vapid;
844
+ if (vapidConfig) {
845
+ configureVapid(vapidConfig);
846
+ console.log('[Gateway] Push notifications enabled (VAPID configured)');
847
+ }
929
848
  const server = http.createServer(async (req, res) => {
930
849
  // CORS headers for web clients
931
850
  res.setHeader('Access-Control-Allow-Origin', '*');
@@ -946,6 +865,11 @@ export async function startGatewayServer(options) {
946
865
  if (req.method === 'GET' && serveStaticFile(req, res, currentApiKeys)) {
947
866
  return;
948
867
  }
868
+ // GET /api/push/vapid-key — public (no auth), returns VAPID public key
869
+ if (routePath === '/api/push/vapid-key' && req.method === 'GET') {
870
+ handleGetVapidKey(res, vapidConfig);
871
+ return;
872
+ }
949
873
  // All other routes require auth
950
874
  if (!authenticate(req, res, currentApiKeys))
951
875
  return;
@@ -975,22 +899,6 @@ export async function startGatewayServer(options) {
975
899
  await handleDeleteThread(params.name, req, res);
976
900
  return;
977
901
  }
978
- // DELETE /api/thread/:name/messages
979
- if (routePath === '/api/thread/:name/messages' && req.method === 'DELETE') {
980
- await handleDeleteMessages(params.name, req, res);
981
- return;
982
- }
983
- // GET /api/thread/:name/config — read thread config
984
- if (routePath === '/api/thread/:name/config' && req.method === 'GET') {
985
- try {
986
- const tc = await loadThreadConfig(params.name);
987
- jsonResponse(res, 200, tc);
988
- }
989
- catch (err) {
990
- jsonResponse(res, 500, { error: String(err) });
991
- }
992
- return;
993
- }
994
902
  // PUT /api/thread/:name/config — update thread config (projectDir, template)
995
903
  if (routePath === '/api/thread/:name/config' && req.method === 'PUT') {
996
904
  await handleUpdateThreadConfig(params.name, req, res);
@@ -998,12 +906,7 @@ export async function startGatewayServer(options) {
998
906
  }
999
907
  // GET /api/threads
1000
908
  if (routePath === '/api/threads' && req.method === 'GET') {
1001
- await handleListThreads(res, executorRegistry);
1002
- return;
1003
- }
1004
- // GET /api/executors
1005
- if (routePath === '/api/executors' && req.method === 'GET') {
1006
- await handleListExecutors(res, executorRegistry);
909
+ await handleListThreads(res);
1007
910
  return;
1008
911
  }
1009
912
  // GET /api/templates — list templates
@@ -1021,16 +924,26 @@ export async function startGatewayServer(options) {
1021
924
  await handleAgentInject(req, res, pipelineOpts);
1022
925
  return;
1023
926
  }
1024
- // POST /api/device/:executorId/tool — route device tool call to executor
1025
- if (routePath === '/api/device/:executorId/tool' && req.method === 'POST') {
1026
- await handleDeviceToolCall(params.executorId, req, res, executorRegistry);
1027
- return;
1028
- }
1029
927
  // POST /api/migrate — sync thread + clone repo + set projectDir
1030
928
  if (routePath === '/api/migrate' && req.method === 'POST') {
1031
929
  await handleMigrate(req, res, pipelineOpts);
1032
930
  return;
1033
931
  }
932
+ // POST /api/push/subscribe — register push subscription
933
+ if (routePath === '/api/push/subscribe' && req.method === 'POST') {
934
+ await handlePushSubscribe(req, res);
935
+ return;
936
+ }
937
+ // DELETE /api/push/subscribe — remove push subscription
938
+ if (routePath === '/api/push/subscribe' && req.method === 'DELETE') {
939
+ await handlePushUnsubscribe(req, res);
940
+ return;
941
+ }
942
+ // POST /api/notify — send push notification
943
+ if (routePath === '/api/notify' && req.method === 'POST') {
944
+ await handleNotify(req, res);
945
+ return;
946
+ }
1034
947
  // 404
1035
948
  jsonResponse(res, 404, { error: 'Not found' });
1036
949
  }
@@ -1041,40 +954,6 @@ export async function startGatewayServer(options) {
1041
954
  }
1042
955
  }
1043
956
  });
1044
- // Handle executor WebSocket upgrades on /ws/executor
1045
- server.on('upgrade', (req, socket, head) => {
1046
- const url = req.url?.split('?')[0] || '/';
1047
- if (url !== '/ws/executor')
1048
- return; // let other handlers (webchat) handle non-executor paths
1049
- // Authenticate via query param
1050
- const queryApiKey = new URL(req.url || '/', `http://${req.headers.host}`).searchParams.get('apiKey');
1051
- if (!queryApiKey || !currentApiKeys.includes(queryApiKey)) {
1052
- socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
1053
- socket.destroy();
1054
- return;
1055
- }
1056
- executorWss.handleUpgrade(req, socket, head, (ws) => {
1057
- // Wait for registration message
1058
- const timeout = setTimeout(() => {
1059
- ws.close(1008, 'Registration timeout');
1060
- }, 10_000);
1061
- ws.once('message', (raw) => {
1062
- clearTimeout(timeout);
1063
- try {
1064
- const data = JSON.parse(raw.toString());
1065
- if (data.type === 'executor_register') {
1066
- executorRegistry.register(data, ws);
1067
- }
1068
- else {
1069
- ws.close(1008, 'Expected executor_register message');
1070
- }
1071
- }
1072
- catch {
1073
- ws.close(1008, 'Invalid registration message');
1074
- }
1075
- });
1076
- });
1077
- });
1078
957
  return new Promise((resolve, reject) => {
1079
958
  server.on('error', reject);
1080
959
  server.listen(port, () => {
@@ -1086,23 +965,13 @@ export async function startGatewayServer(options) {
1086
965
  port: actualPort,
1087
966
  url,
1088
967
  server,
1089
- executorRegistry,
1090
968
  updateApiKeys: (newKeys) => {
1091
969
  currentApiKeys = newKeys;
1092
970
  },
1093
- setBroadcastToThread: (fn) => {
1094
- pipelineOpts.broadcastToThread = fn;
1095
- },
1096
971
  close: async () => {
1097
972
  // Abort any in-flight message processing
1098
973
  threadQueues.clear();
1099
974
  threadBusy.clear();
1100
- executorRegistry.shutdown();
1101
- // Close executor WebSocket server
1102
- for (const client of executorWss.clients) {
1103
- client.close();
1104
- }
1105
- executorWss.close();
1106
975
  return new Promise(resolveClose => {
1107
976
  server.close(() => {
1108
977
  clearThreadCache();