cyrus-edge-worker 0.0.40 → 0.2.0-rc

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.
@@ -18,7 +18,8 @@ export declare class EdgeWorker extends EventEmitter {
18
18
  private repositories;
19
19
  private agentSessionManagers;
20
20
  private linearClients;
21
- private ndjsonClients;
21
+ private linearEventTransport;
22
+ private configUpdater;
22
23
  private persistenceManager;
23
24
  private sharedApplicationServer;
24
25
  private cyrusHome;
@@ -26,12 +27,15 @@ export declare class EdgeWorker extends EventEmitter {
26
27
  private procedureRouter;
27
28
  private configWatcher?;
28
29
  private configPath?;
29
- private tokenToRepoIds;
30
30
  constructor(config: EdgeWorkerConfig);
31
31
  /**
32
32
  * Start the edge worker
33
33
  */
34
34
  start(): Promise<void>;
35
+ /**
36
+ * Initialize and register components (routes) before server starts
37
+ */
38
+ private initializeComponents;
35
39
  /**
36
40
  * Stop the edge worker
37
41
  */
@@ -40,11 +44,6 @@ export declare class EdgeWorker extends EventEmitter {
40
44
  * Set the config file path for dynamic reloading
41
45
  */
42
46
  setConfigPath(configPath: string): void;
43
- /**
44
- * Get fresh list of repositories for a given Linear token
45
- * This ensures webhook handlers always work with current repository state
46
- */
47
- private getRepositoriesForToken;
48
47
  /**
49
48
  * Handle resuming a parent session when a child session completes
50
49
  * This is the core logic used by the resume parent session callback
@@ -83,26 +82,6 @@ export declare class EdgeWorker extends EventEmitter {
83
82
  * Remove deleted repositories
84
83
  */
85
84
  private removeDeletedRepositories;
86
- /**
87
- * Set up webhook listener for a repository
88
- */
89
- private setupWebhookListener;
90
- /**
91
- * Reconnect webhook when token changes
92
- */
93
- private reconnectWebhook;
94
- /**
95
- * Clean up webhook listener if no other repositories use the token
96
- */
97
- private cleanupWebhookIfUnused;
98
- /**
99
- * Handle connection established
100
- */
101
- private handleConnect;
102
- /**
103
- * Handle disconnection
104
- */
105
- private handleDisconnect;
106
85
  /**
107
86
  * Handle errors
108
87
  */
@@ -239,10 +218,10 @@ export declare class EdgeWorker extends EventEmitter {
239
218
  */
240
219
  getConnectionStatus(): Map<string, boolean>;
241
220
  /**
242
- * Get NDJSON client by token (for testing purposes)
221
+ * Get event transport (for testing purposes)
243
222
  * @internal
244
223
  */
245
- _getClientByToken(token: string): any;
224
+ _getClientByToken(_token: string): any;
246
225
  /**
247
226
  * Start OAuth flow using the shared application server
248
227
  */
@@ -1 +1 @@
1
- {"version":3,"file":"EdgeWorker.d.ts","sourceRoot":"","sources":["../src/EdgeWorker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EdgeWorker.d.ts","sourceRoot":"","sources":["../src/EdgeWorker.ts"],"names":[],"mappings":"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"}
@@ -5,9 +5,9 @@ import { fileURLToPath } from "node:url";
5
5
  import { LinearClient, } from "@linear/sdk";
6
6
  import { watch as chokidarWatch } from "chokidar";
7
7
  import { ClaudeRunner, createCyrusToolsServer, createImageToolsServer, createSoraToolsServer, getAllTools, getCoordinatorTools, getReadOnlyTools, getSafeTools, } from "cyrus-claude-runner";
8
- import { isAgentSessionCreatedWebhook, isAgentSessionPromptedWebhook, isIssueAssignedWebhook, isIssueCommentMentionWebhook, isIssueNewCommentWebhook, isIssueUnassignedWebhook, PersistenceManager, } from "cyrus-core";
9
- import { LinearWebhookClient } from "cyrus-linear-webhook-client";
10
- import { NdjsonClient } from "cyrus-ndjson-client";
8
+ import { ConfigUpdater } from "cyrus-config-updater";
9
+ import { DEFAULT_PROXY_URL, isAgentSessionCreatedWebhook, isAgentSessionPromptedWebhook, isIssueAssignedWebhook, isIssueCommentMentionWebhook, isIssueNewCommentWebhook, isIssueUnassignedWebhook, PersistenceManager, resolvePath, } from "cyrus-core";
10
+ import { LinearEventTransport } from "cyrus-linear-event-transport";
11
11
  import { fileTypeFromBuffer } from "file-type";
12
12
  import { AgentSessionManager } from "./AgentSessionManager.js";
13
13
  import { ProcedureRouter, } from "./procedures/index.js";
@@ -23,7 +23,8 @@ export class EdgeWorker extends EventEmitter {
23
23
  repositories = new Map(); // repository 'id' (internal, stored in config.json) mapped to the full repo config
24
24
  agentSessionManagers = new Map(); // Maps repository ID to AgentSessionManager, which manages ClaudeRunners for a repo
25
25
  linearClients = new Map(); // one linear client per 'repository'
26
- ndjsonClients = new Map(); // listeners for webhook events, one per linear token
26
+ linearEventTransport = null; // Single event transport for webhook delivery
27
+ configUpdater = null; // Single config updater for configuration updates
27
28
  persistenceManager;
28
29
  sharedApplicationServer;
29
30
  cyrusHome;
@@ -31,7 +32,6 @@ export class EdgeWorker extends EventEmitter {
31
32
  procedureRouter; // Intelligent workflow routing
32
33
  configWatcher; // File watcher for config.json
33
34
  configPath; // Path to config.json file
34
- tokenToRepoIds = new Map(); // Maps Linear token to repository IDs using that token
35
35
  constructor(config) {
36
36
  super();
37
37
  this.config = config;
@@ -48,15 +48,28 @@ export class EdgeWorker extends EventEmitter {
48
48
  // Initialize shared application server
49
49
  const serverPort = config.serverPort || config.webhookPort || 3456;
50
50
  const serverHost = config.serverHost || "localhost";
51
- this.sharedApplicationServer = new SharedApplicationServer(serverPort, serverHost, config.ngrokAuthToken, config.proxyUrl);
52
- // Register OAuth callback handler if provided
53
- if (config.handlers?.onOAuthCallback) {
54
- this.sharedApplicationServer.registerOAuthCallbackHandler(config.handlers.onOAuthCallback);
55
- }
56
- // Initialize repositories
51
+ this.sharedApplicationServer = new SharedApplicationServer(serverPort, serverHost);
52
+ // Initialize repositories with path resolution
57
53
  for (const repo of config.repositories) {
58
54
  if (repo.isActive !== false) {
59
- this.repositories.set(repo.id, repo);
55
+ // Resolve paths that may contain tilde (~) prefix
56
+ const resolvedRepo = {
57
+ ...repo,
58
+ repositoryPath: resolvePath(repo.repositoryPath),
59
+ workspaceBaseDir: resolvePath(repo.workspaceBaseDir),
60
+ mcpConfigPath: Array.isArray(repo.mcpConfigPath)
61
+ ? repo.mcpConfigPath.map(resolvePath)
62
+ : repo.mcpConfigPath
63
+ ? resolvePath(repo.mcpConfigPath)
64
+ : undefined,
65
+ promptTemplatePath: repo.promptTemplatePath
66
+ ? resolvePath(repo.promptTemplatePath)
67
+ : undefined,
68
+ openaiOutputDirectory: repo.openaiOutputDirectory
69
+ ? resolvePath(repo.openaiOutputDirectory)
70
+ : undefined,
71
+ };
72
+ this.repositories.set(repo.id, resolvedRepo);
60
73
  // Create Linear client for this repository's workspace
61
74
  const linearClient = new LinearClient({
62
75
  accessToken: repo.linearToken,
@@ -123,77 +136,7 @@ export class EdgeWorker extends EventEmitter {
123
136
  this.agentSessionManagers.set(repo.id, agentSessionManager);
124
137
  }
125
138
  }
126
- // Group repositories by token to minimize NDJSON connections
127
- const tokenToRepos = new Map();
128
- for (const repo of this.repositories.values()) {
129
- const repos = tokenToRepos.get(repo.linearToken) || [];
130
- repos.push(repo);
131
- tokenToRepos.set(repo.linearToken, repos);
132
- // Track token-to-repo-id mapping for dynamic config updates
133
- const repoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
134
- if (!repoIds.includes(repo.id)) {
135
- repoIds.push(repo.id);
136
- }
137
- this.tokenToRepoIds.set(repo.linearToken, repoIds);
138
- }
139
- // Create one NDJSON client per unique token using shared application server
140
- for (const [token, repos] of tokenToRepos) {
141
- if (!repos || repos.length === 0)
142
- continue;
143
- const firstRepo = repos[0];
144
- if (!firstRepo)
145
- continue;
146
- const primaryRepoId = firstRepo.id;
147
- // Determine which client to use based on environment variable
148
- const useLinearDirectWebhooks = process.env.LINEAR_DIRECT_WEBHOOKS?.toLowerCase().trim() === "true";
149
- const clientConfig = {
150
- proxyUrl: config.proxyUrl,
151
- token: token,
152
- name: repos.map((r) => r.name).join(", "), // Pass repository names
153
- transport: "webhook",
154
- // Use shared application server instead of individual servers
155
- useExternalWebhookServer: true,
156
- externalWebhookServer: this.sharedApplicationServer,
157
- webhookPort: serverPort, // All clients use same port
158
- webhookPath: "/webhook",
159
- webhookHost: serverHost,
160
- ...(config.baseUrl && { webhookBaseUrl: config.baseUrl }),
161
- // Legacy fallback support
162
- ...(!config.baseUrl &&
163
- config.webhookBaseUrl && { webhookBaseUrl: config.webhookBaseUrl }),
164
- onConnect: () => this.handleConnect(primaryRepoId, repos),
165
- onDisconnect: (reason) => this.handleDisconnect(primaryRepoId, repos, reason),
166
- onError: (error) => this.handleError(error),
167
- };
168
- // Create the appropriate client based on configuration
169
- const ndjsonClient = useLinearDirectWebhooks
170
- ? new LinearWebhookClient({
171
- ...clientConfig,
172
- onWebhook: (payload) => {
173
- // Get fresh repositories for this token to avoid stale closures
174
- const freshRepos = this.getRepositoriesForToken(token);
175
- this.handleWebhook(payload, freshRepos);
176
- },
177
- })
178
- : new NdjsonClient(clientConfig);
179
- // Set up webhook handler for NdjsonClient (LinearWebhookClient uses onWebhook in constructor)
180
- if (!useLinearDirectWebhooks) {
181
- ndjsonClient.on("webhook", (data) => {
182
- // Get fresh repositories for this token to avoid stale closures
183
- const freshRepos = this.getRepositoriesForToken(token);
184
- this.handleWebhook(data, freshRepos);
185
- });
186
- }
187
- // Optional heartbeat logging (only for NdjsonClient)
188
- if (process.env.DEBUG_EDGE === "true" && !useLinearDirectWebhooks) {
189
- ndjsonClient.on("heartbeat", () => {
190
- console.log(`❤️ Heartbeat received for token ending in ...${token.slice(-4)}`);
191
- });
192
- }
193
- // Store with the first repo's ID as the key (for error messages)
194
- // But also store the token mapping for lookup
195
- this.ndjsonClients.set(primaryRepoId, ndjsonClient);
196
- }
139
+ // Components will be initialized and registered in start() method before server starts
197
140
  }
198
141
  /**
199
142
  * Start the edge worker
@@ -205,52 +148,53 @@ export class EdgeWorker extends EventEmitter {
205
148
  if (this.configPath) {
206
149
  this.startConfigWatcher();
207
150
  }
208
- // Start shared application server first
151
+ // Initialize and register components BEFORE starting server (routes must be registered before listen())
152
+ await this.initializeComponents();
153
+ // Start shared application server (this also starts Cloudflare tunnel if CLOUDFLARE_TOKEN is set)
209
154
  await this.sharedApplicationServer.start();
210
- // Connect all NDJSON clients
211
- const connections = Array.from(this.ndjsonClients.entries()).map(async ([repoId, client]) => {
212
- try {
213
- await client.connect();
214
- }
215
- catch (error) {
216
- const repoConfig = this.config.repositories.find((r) => r.id === repoId);
217
- const repoName = repoConfig?.name || repoId;
218
- // Check if it's an authentication error
219
- if (error.isAuthError || error.code === "LINEAR_AUTH_FAILED") {
220
- console.error(`\n❌ Linear authentication failed for repository: ${repoName}`);
221
- console.error(` Workspace: ${repoConfig?.linearWorkspaceName || repoConfig?.linearWorkspaceId || "Unknown"}`);
222
- console.error(` Error: ${error.message}`);
223
- console.error(`\n To fix this issue:`);
224
- console.error(` 1. Run: cyrus refresh-token`);
225
- console.error(` 2. Complete the OAuth flow in your browser`);
226
- console.error(` 3. The configuration will be automatically updated\n`);
227
- console.error(` You can also check all tokens with: cyrus check-tokens\n`);
228
- // Continue with other repositories instead of failing completely
229
- return { repoId, success: false, error };
230
- }
231
- // For other errors, still log but with less guidance
232
- console.error(`\n❌ Failed to connect repository: ${repoName}`);
233
- console.error(` Error: ${error.message}\n`);
234
- return { repoId, success: false, error };
235
- }
236
- return { repoId, success: true };
155
+ }
156
+ /**
157
+ * Initialize and register components (routes) before server starts
158
+ */
159
+ async initializeComponents() {
160
+ // Get the first active repository for configuration
161
+ const firstRepo = Array.from(this.repositories.values())[0];
162
+ if (!firstRepo) {
163
+ throw new Error("No active repositories configured");
164
+ }
165
+ // 1. Create and register LinearEventTransport
166
+ const useDirectWebhooks = process.env.LINEAR_DIRECT_WEBHOOKS?.toLowerCase() === "true";
167
+ const verificationMode = useDirectWebhooks ? "direct" : "proxy";
168
+ // Get appropriate secret based on mode
169
+ const secret = useDirectWebhooks
170
+ ? process.env.LINEAR_WEBHOOK_SECRET || ""
171
+ : process.env.CYRUS_API_KEY || "";
172
+ this.linearEventTransport = new LinearEventTransport({
173
+ fastifyServer: this.sharedApplicationServer.getFastifyInstance(),
174
+ verificationMode,
175
+ secret,
237
176
  });
238
- const results = await Promise.all(connections);
239
- const failures = results.filter((r) => !r.success);
240
- if (failures.length === this.ndjsonClients.size) {
241
- // All connections failed
242
- throw new Error("Failed to connect any repositories. Please check your configuration and Linear tokens.");
243
- }
244
- else if (failures.length > 0) {
245
- // Some connections failed
246
- console.warn(`\n⚠️ Connected ${results.length - failures.length} out of ${results.length} repositories`);
247
- console.warn(` The following repositories could not be connected:`);
248
- failures.forEach((f) => {
249
- const repoConfig = this.config.repositories.find((r) => r.id === f.repoId);
250
- console.warn(` - ${repoConfig?.name || f.repoId}`);
251
- });
252
- console.warn(`\n Cyrus will continue running with the available repositories.\n`);
253
- }
177
+ // Listen for webhook events
178
+ this.linearEventTransport.on("webhook", (payload) => {
179
+ // Get all active repositories for webhook handling
180
+ const repos = Array.from(this.repositories.values());
181
+ this.handleWebhook(payload, repos);
182
+ });
183
+ // Listen for errors
184
+ this.linearEventTransport.on("error", (error) => {
185
+ this.handleError(error);
186
+ });
187
+ // Register the /webhook endpoint
188
+ this.linearEventTransport.register();
189
+ console.log(`✅ Linear event transport registered (${verificationMode} mode)`);
190
+ console.log(` Webhook endpoint: ${this.sharedApplicationServer.getWebhookUrl()}`);
191
+ // 2. Create and register ConfigUpdater
192
+ this.configUpdater = new ConfigUpdater(this.sharedApplicationServer.getFastifyInstance(), this.cyrusHome, process.env.CYRUS_API_KEY || "");
193
+ // Register config update routes
194
+ this.configUpdater.register();
195
+ console.log("✅ Config updater registered");
196
+ console.log(" Routes: /api/update/cyrus-config, /api/update/cyrus-env,");
197
+ console.log(" /api/update/repository, /api/test-mcp, /api/configure-mcp");
254
198
  }
255
199
  /**
256
200
  * Stop the edge worker
@@ -285,11 +229,10 @@ export class EdgeWorker extends EventEmitter {
285
229
  }
286
230
  }
287
231
  }
288
- // Disconnect all NDJSON clients
289
- for (const client of this.ndjsonClients.values()) {
290
- client.disconnect();
291
- }
292
- // Stop shared application server
232
+ // Clear event transport (no explicit cleanup needed, routes are removed when server stops)
233
+ this.linearEventTransport = null;
234
+ this.configUpdater = null;
235
+ // Stop shared application server (this also stops Cloudflare tunnel if running)
293
236
  await this.sharedApplicationServer.stop();
294
237
  }
295
238
  /**
@@ -298,21 +241,6 @@ export class EdgeWorker extends EventEmitter {
298
241
  setConfigPath(configPath) {
299
242
  this.configPath = configPath;
300
243
  }
301
- /**
302
- * Get fresh list of repositories for a given Linear token
303
- * This ensures webhook handlers always work with current repository state
304
- */
305
- getRepositoriesForToken(token) {
306
- const repoIds = this.tokenToRepoIds.get(token) || [];
307
- const repos = [];
308
- for (const repoId of repoIds) {
309
- const repo = this.repositories.get(repoId);
310
- if (repo) {
311
- repos.push(repo);
312
- }
313
- }
314
- return repos;
315
- }
316
244
  /**
317
245
  * Handle resuming a parent session when a child session completes
318
246
  * This is the core logic used by the resume parent session callback
@@ -521,8 +449,25 @@ export class EdgeWorker extends EventEmitter {
521
449
  }
522
450
  try {
523
451
  console.log(`➕ Adding repository: ${repo.name} (${repo.id})`);
452
+ // Resolve paths that may contain tilde (~) prefix
453
+ const resolvedRepo = {
454
+ ...repo,
455
+ repositoryPath: resolvePath(repo.repositoryPath),
456
+ workspaceBaseDir: resolvePath(repo.workspaceBaseDir),
457
+ mcpConfigPath: Array.isArray(repo.mcpConfigPath)
458
+ ? repo.mcpConfigPath.map(resolvePath)
459
+ : repo.mcpConfigPath
460
+ ? resolvePath(repo.mcpConfigPath)
461
+ : undefined,
462
+ promptTemplatePath: repo.promptTemplatePath
463
+ ? resolvePath(repo.promptTemplatePath)
464
+ : undefined,
465
+ openaiOutputDirectory: repo.openaiOutputDirectory
466
+ ? resolvePath(repo.openaiOutputDirectory)
467
+ : undefined,
468
+ };
524
469
  // Add to internal map
525
- this.repositories.set(repo.id, repo);
470
+ this.repositories.set(repo.id, resolvedRepo);
526
471
  // Create Linear client
527
472
  const linearClient = new LinearClient({
528
473
  accessToken: repo.linearToken,
@@ -536,14 +481,6 @@ export class EdgeWorker extends EventEmitter {
536
481
  }, undefined, // No resumeNextSubroutine callback for dynamically added repos
537
482
  this.procedureRouter, this.sharedApplicationServer);
538
483
  this.agentSessionManagers.set(repo.id, agentSessionManager);
539
- // Update token-to-repo mapping
540
- const repoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
541
- if (!repoIds.includes(repo.id)) {
542
- repoIds.push(repo.id);
543
- }
544
- this.tokenToRepoIds.set(repo.linearToken, repoIds);
545
- // Set up webhook listener
546
- await this.setupWebhookListener(repo);
547
484
  console.log(`✅ Repository added successfully: ${repo.name}`);
548
485
  }
549
486
  catch (error) {
@@ -563,8 +500,25 @@ export class EdgeWorker extends EventEmitter {
563
500
  continue;
564
501
  }
565
502
  console.log(`🔄 Updating repository: ${repo.name} (${repo.id})`);
503
+ // Resolve paths that may contain tilde (~) prefix
504
+ const resolvedRepo = {
505
+ ...repo,
506
+ repositoryPath: resolvePath(repo.repositoryPath),
507
+ workspaceBaseDir: resolvePath(repo.workspaceBaseDir),
508
+ mcpConfigPath: Array.isArray(repo.mcpConfigPath)
509
+ ? repo.mcpConfigPath.map(resolvePath)
510
+ : repo.mcpConfigPath
511
+ ? resolvePath(repo.mcpConfigPath)
512
+ : undefined,
513
+ promptTemplatePath: repo.promptTemplatePath
514
+ ? resolvePath(repo.promptTemplatePath)
515
+ : undefined,
516
+ openaiOutputDirectory: repo.openaiOutputDirectory
517
+ ? resolvePath(repo.openaiOutputDirectory)
518
+ : undefined,
519
+ };
566
520
  // Update stored config
567
- this.repositories.set(repo.id, repo);
521
+ this.repositories.set(repo.id, resolvedRepo);
568
522
  // If token changed, recreate Linear client
569
523
  if (oldRepo.linearToken !== repo.linearToken) {
570
524
  console.log(` 🔑 Token changed, recreating Linear client`);
@@ -572,22 +526,6 @@ export class EdgeWorker extends EventEmitter {
572
526
  accessToken: repo.linearToken,
573
527
  });
574
528
  this.linearClients.set(repo.id, linearClient);
575
- // Update token mapping
576
- const oldRepoIds = this.tokenToRepoIds.get(oldRepo.linearToken) || [];
577
- const filteredOldIds = oldRepoIds.filter((id) => id !== repo.id);
578
- if (filteredOldIds.length > 0) {
579
- this.tokenToRepoIds.set(oldRepo.linearToken, filteredOldIds);
580
- }
581
- else {
582
- this.tokenToRepoIds.delete(oldRepo.linearToken);
583
- }
584
- const newRepoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
585
- if (!newRepoIds.includes(repo.id)) {
586
- newRepoIds.push(repo.id);
587
- }
588
- this.tokenToRepoIds.set(repo.linearToken, newRepoIds);
589
- // Reconnect webhook if needed
590
- await this.reconnectWebhook(oldRepo, repo);
591
529
  }
592
530
  // If active status changed
593
531
  if (oldRepo.isActive !== repo.isActive) {
@@ -596,7 +534,6 @@ export class EdgeWorker extends EventEmitter {
596
534
  }
597
535
  else {
598
536
  console.log(` ▶️ Repository reactivated`);
599
- await this.setupWebhookListener(repo);
600
537
  }
601
538
  }
602
539
  console.log(`✅ Repository updated successfully: ${repo.name}`);
@@ -651,17 +588,6 @@ export class EdgeWorker extends EventEmitter {
651
588
  this.repositories.delete(repo.id);
652
589
  this.linearClients.delete(repo.id);
653
590
  this.agentSessionManagers.delete(repo.id);
654
- // Update token mapping
655
- const repoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
656
- const filteredIds = repoIds.filter((id) => id !== repo.id);
657
- if (filteredIds.length > 0) {
658
- this.tokenToRepoIds.set(repo.linearToken, filteredIds);
659
- }
660
- else {
661
- this.tokenToRepoIds.delete(repo.linearToken);
662
- }
663
- // Clean up webhook listener if no other repos use the same token
664
- await this.cleanupWebhookIfUnused(repo);
665
591
  console.log(`✅ Repository removed successfully: ${repo.name}`);
666
592
  }
667
593
  catch (error) {
@@ -669,115 +595,6 @@ export class EdgeWorker extends EventEmitter {
669
595
  }
670
596
  }
671
597
  }
672
- /**
673
- * Set up webhook listener for a repository
674
- */
675
- async setupWebhookListener(repo) {
676
- // Check if we already have a client for this token
677
- const existingRepoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
678
- const existingClient = existingRepoIds.length > 0
679
- ? this.ndjsonClients.get(existingRepoIds[0] || "")
680
- : null;
681
- if (existingClient) {
682
- console.log(` ℹ️ Reusing existing webhook connection for token ...${repo.linearToken.slice(-4)}`);
683
- return;
684
- }
685
- // Create new NDJSON client for this token
686
- const serverPort = this.config.serverPort || this.config.webhookPort || 3456;
687
- const serverHost = this.config.serverHost || "localhost";
688
- const useLinearDirectWebhooks = process.env.LINEAR_DIRECT_WEBHOOKS?.toLowerCase().trim() === "true";
689
- const clientConfig = {
690
- proxyUrl: this.config.proxyUrl,
691
- token: repo.linearToken,
692
- name: repo.name,
693
- transport: "webhook",
694
- useExternalWebhookServer: true,
695
- externalWebhookServer: this.sharedApplicationServer,
696
- webhookPort: serverPort,
697
- webhookPath: "/webhook",
698
- webhookHost: serverHost,
699
- ...(this.config.baseUrl && { webhookBaseUrl: this.config.baseUrl }),
700
- ...(!this.config.baseUrl &&
701
- this.config.webhookBaseUrl && {
702
- webhookBaseUrl: this.config.webhookBaseUrl,
703
- }),
704
- onConnect: () => this.handleConnect(repo.id, [repo]),
705
- onDisconnect: (reason) => this.handleDisconnect(repo.id, [repo], reason),
706
- onError: (error) => this.handleError(error),
707
- };
708
- const ndjsonClient = useLinearDirectWebhooks
709
- ? new LinearWebhookClient({
710
- ...clientConfig,
711
- onWebhook: (payload) => {
712
- // Get fresh repositories for this token to avoid stale closures
713
- const freshRepos = this.getRepositoriesForToken(repo.linearToken);
714
- this.handleWebhook(payload, freshRepos);
715
- },
716
- })
717
- : new NdjsonClient(clientConfig);
718
- if (!useLinearDirectWebhooks) {
719
- ndjsonClient.on("webhook", (data) => {
720
- // Get fresh repositories for this token to avoid stale closures
721
- const freshRepos = this.getRepositoriesForToken(repo.linearToken);
722
- this.handleWebhook(data, freshRepos);
723
- });
724
- }
725
- this.ndjsonClients.set(repo.id, ndjsonClient);
726
- // Connect the client
727
- try {
728
- await ndjsonClient.connect();
729
- console.log(` ✅ Webhook listener connected for ${repo.name}`);
730
- }
731
- catch (error) {
732
- console.error(` ❌ Failed to connect webhook listener:`, error);
733
- }
734
- }
735
- /**
736
- * Reconnect webhook when token changes
737
- */
738
- async reconnectWebhook(oldRepo, newRepo) {
739
- console.log(` 🔌 Reconnecting webhook due to token change`);
740
- // Disconnect old client if no other repos use it
741
- await this.cleanupWebhookIfUnused(oldRepo);
742
- // Set up new connection
743
- await this.setupWebhookListener(newRepo);
744
- }
745
- /**
746
- * Clean up webhook listener if no other repositories use the token
747
- */
748
- async cleanupWebhookIfUnused(repo) {
749
- const repoIds = this.tokenToRepoIds.get(repo.linearToken) || [];
750
- const otherRepos = repoIds.filter((id) => id !== repo.id);
751
- if (otherRepos.length === 0) {
752
- // No other repos use this token, safe to disconnect
753
- const client = this.ndjsonClients.get(repo.id);
754
- if (client) {
755
- console.log(` 🔌 Disconnecting webhook for token ...${repo.linearToken.slice(-4)}`);
756
- client.disconnect();
757
- this.ndjsonClients.delete(repo.id);
758
- }
759
- }
760
- else {
761
- console.log(` ℹ️ Token still used by ${otherRepos.length} other repository(ies), keeping connection`);
762
- }
763
- }
764
- /**
765
- * Handle connection established
766
- */
767
- handleConnect(clientId, repos) {
768
- // Get the token for backward compatibility with events
769
- const token = repos[0]?.linearToken || clientId;
770
- this.emit("connected", token);
771
- // Connection logged by CLI app event handler
772
- }
773
- /**
774
- * Handle disconnection
775
- */
776
- handleDisconnect(clientId, repos, reason) {
777
- // Get the token for backward compatibility with events
778
- const token = repos[0]?.linearToken || clientId;
779
- this.emit("disconnected", token, reason);
780
- }
781
598
  /**
782
599
  * Handle errors
783
600
  */
@@ -1949,29 +1766,28 @@ ${newComment ? `New comment to address:\n${newComment.body}\n\n` : ""}Please ana
1949
1766
  */
1950
1767
  getConnectionStatus() {
1951
1768
  const status = new Map();
1952
- for (const [repoId, client] of this.ndjsonClients) {
1953
- status.set(repoId, client.isConnected());
1769
+ // Single event transport is "connected" if it exists
1770
+ if (this.linearEventTransport) {
1771
+ // Mark all repositories as connected since they share the single transport
1772
+ for (const repoId of this.repositories.keys()) {
1773
+ status.set(repoId, true);
1774
+ }
1954
1775
  }
1955
1776
  return status;
1956
1777
  }
1957
1778
  /**
1958
- * Get NDJSON client by token (for testing purposes)
1779
+ * Get event transport (for testing purposes)
1959
1780
  * @internal
1960
1781
  */
1961
- _getClientByToken(token) {
1962
- for (const [repoId, client] of this.ndjsonClients) {
1963
- const repo = this.repositories.get(repoId);
1964
- if (repo?.linearToken === token) {
1965
- return client;
1966
- }
1967
- }
1968
- return undefined;
1782
+ _getClientByToken(_token) {
1783
+ // Return the single shared event transport
1784
+ return this.linearEventTransport;
1969
1785
  }
1970
1786
  /**
1971
1787
  * Start OAuth flow using the shared application server
1972
1788
  */
1973
1789
  async startOAuthFlow(proxyUrl) {
1974
- const oauthProxyUrl = proxyUrl || this.config.proxyUrl;
1790
+ const oauthProxyUrl = proxyUrl || this.config.proxyUrl || DEFAULT_PROXY_URL;
1975
1791
  return this.sharedApplicationServer.startOAuthFlow(oauthProxyUrl);
1976
1792
  }
1977
1793
  /**