@wolpertingerlabs/drawlatch 1.0.0-alpha.2 → 1.0.0-alpha.29

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 (119) hide show
  1. package/CONNECTIONS.md +3 -0
  2. package/README.md +395 -465
  3. package/bin/drawlatch.js +1018 -49
  4. package/dist/auth/auth.d.ts +10 -0
  5. package/dist/auth/auth.js +146 -0
  6. package/dist/auth/env-writer.d.ts +6 -0
  7. package/dist/auth/env-writer.js +52 -0
  8. package/dist/auth/password.d.ts +8 -0
  9. package/dist/auth/password.js +28 -0
  10. package/dist/auth/sessions.d.ts +13 -0
  11. package/dist/auth/sessions.js +85 -0
  12. package/dist/cli/generate-keys.d.ts +4 -4
  13. package/dist/cli/generate-keys.js +30 -25
  14. package/dist/connections/{anthropic.json → ai/anthropic.json} +19 -1
  15. package/dist/connections/{devin.json → ai/devin.json} +7 -1
  16. package/dist/connections/{google-ai.json → ai/google-ai.json} +7 -1
  17. package/dist/connections/{openai.json → ai/openai.json} +7 -1
  18. package/dist/connections/{openrouter.json → ai/openrouter.json} +7 -1
  19. package/dist/connections/developer-tools/github.json +138 -0
  20. package/dist/connections/{hex.json → developer-tools/hex.json} +7 -1
  21. package/dist/connections/developer-tools/linear.json +75 -0
  22. package/dist/connections/{lichess.json → gaming/lichess.json} +7 -1
  23. package/dist/connections/messaging/agentmail.json +21 -0
  24. package/dist/connections/messaging/discord-bot.json +114 -0
  25. package/dist/connections/{discord-oauth.json → messaging/discord-oauth.json} +7 -1
  26. package/dist/connections/messaging/slack.json +75 -0
  27. package/dist/connections/messaging/telegram.json +66 -0
  28. package/dist/connections/{google.json → productivity/google.json} +7 -1
  29. package/dist/connections/productivity/notion.json +75 -0
  30. package/dist/connections/productivity/stripe.json +74 -0
  31. package/dist/connections/productivity/trello.json +113 -0
  32. package/dist/connections/{bluesky.json → social-media/bluesky.json} +41 -0
  33. package/dist/connections/social-media/mastodon.json +65 -0
  34. package/dist/connections/social-media/reddit.json +80 -0
  35. package/dist/connections/{twitch.json → social-media/twitch.json} +40 -0
  36. package/dist/connections/social-media/x.json +67 -0
  37. package/dist/mcp/server.js +544 -31
  38. package/dist/remote/admin-mutations.d.ts +37 -0
  39. package/dist/remote/admin-mutations.js +251 -0
  40. package/dist/remote/admin-types.d.ts +149 -0
  41. package/dist/remote/admin-types.js +11 -0
  42. package/dist/remote/admin.d.ts +37 -0
  43. package/dist/remote/admin.js +316 -0
  44. package/dist/remote/caller-bootstrap.d.ts +71 -0
  45. package/dist/remote/caller-bootstrap.js +141 -0
  46. package/dist/remote/ingestors/base-ingestor.d.ts +19 -3
  47. package/dist/remote/ingestors/base-ingestor.js +27 -6
  48. package/dist/remote/ingestors/discord/discord-gateway.d.ts +2 -2
  49. package/dist/remote/ingestors/discord/discord-gateway.js +29 -6
  50. package/dist/remote/ingestors/e2e/setup.d.ts +69 -0
  51. package/dist/remote/ingestors/e2e/setup.js +147 -0
  52. package/dist/remote/ingestors/manager.d.ts +110 -10
  53. package/dist/remote/ingestors/manager.js +449 -42
  54. package/dist/remote/ingestors/poll/poll-ingestor.d.ts +4 -2
  55. package/dist/remote/ingestors/poll/poll-ingestor.js +26 -5
  56. package/dist/remote/ingestors/registry.d.ts +2 -2
  57. package/dist/remote/ingestors/registry.js +2 -2
  58. package/dist/remote/ingestors/slack/socket-mode.d.ts +2 -2
  59. package/dist/remote/ingestors/slack/socket-mode.js +28 -6
  60. package/dist/remote/ingestors/types.d.ts +25 -0
  61. package/dist/remote/ingestors/webhook/base-webhook-ingestor.d.ts +46 -7
  62. package/dist/remote/ingestors/webhook/base-webhook-ingestor.js +115 -10
  63. package/dist/remote/ingestors/webhook/github-webhook-ingestor.d.ts +30 -0
  64. package/dist/remote/ingestors/webhook/github-webhook-ingestor.js +73 -2
  65. package/dist/remote/ingestors/webhook/lifecycle-types.d.ts +63 -0
  66. package/dist/remote/ingestors/webhook/lifecycle-types.js +12 -0
  67. package/dist/remote/ingestors/webhook/stripe-webhook-ingestor.js +2 -2
  68. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.d.ts +17 -5
  69. package/dist/remote/ingestors/webhook/trello-webhook-ingestor.js +32 -26
  70. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.d.ts +46 -0
  71. package/dist/remote/ingestors/webhook/webhook-lifecycle-manager.js +261 -0
  72. package/dist/remote/server.d.ts +25 -33
  73. package/dist/remote/server.js +681 -195
  74. package/dist/remote/tool-dispatch.d.ts +84 -0
  75. package/dist/remote/tool-dispatch.js +910 -0
  76. package/dist/remote/triggers/rule-engine.d.ts +40 -0
  77. package/dist/remote/triggers/rule-engine.js +228 -0
  78. package/dist/remote/triggers/types.d.ts +69 -0
  79. package/dist/remote/triggers/types.js +10 -0
  80. package/dist/remote/tunnel-state.d.ts +14 -0
  81. package/dist/remote/tunnel-state.js +20 -0
  82. package/dist/remote/tunnel.d.ts +53 -0
  83. package/dist/remote/tunnel.js +149 -0
  84. package/dist/shared/config.d.ts +97 -25
  85. package/dist/shared/config.js +48 -35
  86. package/dist/shared/connections.d.ts +21 -5
  87. package/dist/shared/connections.js +56 -14
  88. package/dist/shared/crypto/index.d.ts +1 -0
  89. package/dist/shared/crypto/index.js +1 -0
  90. package/dist/shared/crypto/key-manager.d.ts +67 -0
  91. package/dist/shared/crypto/key-manager.js +152 -0
  92. package/dist/shared/env-utils.d.ts +42 -0
  93. package/dist/shared/env-utils.js +150 -0
  94. package/dist/shared/listener-config.d.ts +157 -0
  95. package/dist/shared/listener-config.js +10 -0
  96. package/dist/shared/migrations.d.ts +40 -0
  97. package/dist/shared/migrations.js +122 -0
  98. package/dist/shared/protocol/handshake.js +14 -1
  99. package/dist/shared/protocol/index.d.ts +2 -0
  100. package/dist/shared/protocol/index.js +2 -0
  101. package/dist/shared/protocol/sync-client.d.ts +52 -0
  102. package/dist/shared/protocol/sync-client.js +99 -0
  103. package/dist/shared/protocol/sync.d.ts +71 -0
  104. package/dist/shared/protocol/sync.js +176 -0
  105. package/frontend/dist/assets/index-D4k5QKBP.css +1 -0
  106. package/frontend/dist/assets/index-IzwnSOmu.js +267 -0
  107. package/frontend/dist/index.html +15 -0
  108. package/package.json +66 -13
  109. package/dist/connections/discord-bot.json +0 -24
  110. package/dist/connections/github.json +0 -25
  111. package/dist/connections/linear.json +0 -29
  112. package/dist/connections/mastodon.json +0 -25
  113. package/dist/connections/notion.json +0 -33
  114. package/dist/connections/reddit.json +0 -28
  115. package/dist/connections/slack.json +0 -23
  116. package/dist/connections/stripe.json +0 -25
  117. package/dist/connections/telegram.json +0 -26
  118. package/dist/connections/trello.json +0 -25
  119. package/dist/connections/x.json +0 -27
@@ -37,9 +37,9 @@ export declare class DiscordGatewayIngestor extends BaseIngestor {
37
37
  private readonly guildIds;
38
38
  private readonly channelIds;
39
39
  private readonly userIds;
40
- constructor(connectionAlias: string, secrets: Record<string, string>, wsConfig: WebSocketIngestorConfig, bufferSize?: number);
40
+ constructor(connectionAlias: string, secrets: Record<string, string>, wsConfig: WebSocketIngestorConfig, bufferSize?: number, instanceId?: string);
41
41
  start(): Promise<void>;
42
- stop(): Promise<void>;
42
+ stop(_permanent?: boolean): Promise<void>;
43
43
  private connect;
44
44
  private handlePayload;
45
45
  private handleHello;
@@ -41,8 +41,8 @@ export class DiscordGatewayIngestor extends BaseIngestor {
41
41
  guildIds;
42
42
  channelIds;
43
43
  userIds;
44
- constructor(connectionAlias, secrets, wsConfig, bufferSize) {
45
- super(connectionAlias, 'websocket', secrets, bufferSize);
44
+ constructor(connectionAlias, secrets, wsConfig, bufferSize, instanceId) {
45
+ super(connectionAlias, 'websocket', secrets, bufferSize, instanceId);
46
46
  this.wsConfig = wsConfig;
47
47
  this.gatewayUrl = wsConfig.gatewayUrl;
48
48
  this.intents = wsConfig.intents ?? DEFAULT_INTENTS;
@@ -56,12 +56,35 @@ export class DiscordGatewayIngestor extends BaseIngestor {
56
56
  this.connect(this.gatewayUrl);
57
57
  return Promise.resolve();
58
58
  }
59
- stop() {
59
+ stop(_permanent) {
60
60
  this.state = 'stopped';
61
61
  this.clearAllTimers();
62
62
  if (this.ws) {
63
- this.ws.close(1000, 'Shutting down');
63
+ const ws = this.ws;
64
64
  this.ws = null;
65
+ // Wait for the WebSocket to fully close before resolving.
66
+ // This prevents a race where the old connection is still alive when a new
67
+ // ingestor starts. For Discord, this avoids the old gateway session
68
+ // lingering while a new IDENTIFY is sent (which Discord may reject
69
+ // if it sees two sessions for the same bot token).
70
+ return new Promise((resolve) => {
71
+ const onClose = () => {
72
+ ws.removeEventListener('close', onClose);
73
+ resolve();
74
+ };
75
+ if (ws.readyState === WebSocket.CLOSED) {
76
+ resolve();
77
+ }
78
+ else {
79
+ ws.addEventListener('close', onClose);
80
+ ws.close(1000, 'Shutting down');
81
+ // Safety timeout — don't block forever if close event never fires
82
+ setTimeout(() => {
83
+ ws.removeEventListener('close', onClose);
84
+ resolve();
85
+ }, 5000);
86
+ }
87
+ });
65
88
  }
66
89
  return Promise.resolve();
67
90
  }
@@ -331,11 +354,11 @@ export class DiscordGatewayIngestor extends BaseIngestor {
331
354
  }
332
355
  }
333
356
  // ── Self-registration ────────────────────────────────────────────────────
334
- registerIngestorFactory('websocket:discord', (connectionAlias, config, secrets, bufferSize) => {
357
+ registerIngestorFactory('websocket:discord', (connectionAlias, config, secrets, bufferSize, instanceId) => {
335
358
  if (!config.websocket) {
336
359
  log.error(`Missing websocket config for ${connectionAlias}`);
337
360
  return null;
338
361
  }
339
- return new DiscordGatewayIngestor(connectionAlias, secrets, config.websocket, bufferSize);
362
+ return new DiscordGatewayIngestor(connectionAlias, secrets, config.websocket, bufferSize, instanceId);
340
363
  });
341
364
  //# sourceMappingURL=discord-gateway.js.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Shared E2E test setup for connection ingestor tests.
3
+ *
4
+ * Provides helpers to:
5
+ * - Load .env.e2e environment variables
6
+ * - Build a RemoteServerConfig with in-memory keys and requested connections
7
+ * - Boot an Express server on a random port
8
+ * - Generate valid webhook signatures (GitHub, Trello)
9
+ * - Wait for ingestor state transitions and event arrival
10
+ */
11
+ import type { Server } from 'node:http';
12
+ import { IngestorManager } from '../manager.js';
13
+ import type { RemoteServerConfig } from '../../../shared/config.js';
14
+ import type { IngestedEvent, IngestorState } from '../types.js';
15
+ export declare function loadE2EEnv(): void;
16
+ /**
17
+ * Check that all required env vars are set.
18
+ * Returns the missing var names (empty array = all present).
19
+ */
20
+ export declare function checkEnvVars(vars: string[]): string[];
21
+ /**
22
+ * Build a RemoteServerConfig for E2E tests.
23
+ *
24
+ * Creates a single caller ("e2e-client") with the requested connections.
25
+ * Env vars are injected into caller.env so secret resolution works against
26
+ * process.env (already loaded by loadE2EEnv).
27
+ */
28
+ export declare function buildE2EConfig(connections: string[], opts?: {
29
+ env?: Record<string, string>;
30
+ ingestorOverrides?: Record<string, Record<string, unknown>>;
31
+ }): RemoteServerConfig;
32
+ export interface E2EServer {
33
+ server: Server;
34
+ baseUrl: string;
35
+ ingestorManager: IngestorManager;
36
+ /** Gracefully shut down server and stop all ingestors. */
37
+ teardown: () => Promise<void>;
38
+ }
39
+ /**
40
+ * Boot an Express server with the given config on a random port.
41
+ * Starts all ingestors and returns handles for testing.
42
+ */
43
+ export declare function bootServer(config: RemoteServerConfig): Promise<E2EServer>;
44
+ /** Generate a valid GitHub HMAC-SHA256 signature for a raw body. */
45
+ export declare function signGitHubPayload(rawBody: string | Buffer, secret: string): string;
46
+ /** Generate a valid Trello HMAC-SHA1 base64 signature for body + callbackUrl. */
47
+ export declare function signTrelloPayload(rawBody: string | Buffer, callbackUrl: string, secret: string): string;
48
+ /**
49
+ * Wait for an ingestor to reach a specific state.
50
+ * Polls every 250ms until the timeout is reached.
51
+ */
52
+ export declare function waitForIngestorState(manager: IngestorManager, callerAlias: string, connection: string, targetState: IngestorState, timeoutMs?: number): Promise<void>;
53
+ /**
54
+ * Poll until at least one event appears for a connection.
55
+ * Returns the events array once non-empty.
56
+ */
57
+ export declare function pollUntilEvent(manager: IngestorManager, callerAlias: string, connection: string, timeoutMs?: number): Promise<IngestedEvent[]>;
58
+ /** Common shape every IngestedEvent must satisfy (for use with toMatchObject). */
59
+ export declare const INGESTED_EVENT_SHAPE: {
60
+ id: any;
61
+ idempotencyKey: any;
62
+ receivedAt: any;
63
+ receivedAtMs: any;
64
+ callerAlias: string;
65
+ source: any;
66
+ eventType: any;
67
+ data: any;
68
+ };
69
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Shared E2E test setup for connection ingestor tests.
3
+ *
4
+ * Provides helpers to:
5
+ * - Load .env.e2e environment variables
6
+ * - Build a RemoteServerConfig with in-memory keys and requested connections
7
+ * - Boot an Express server on a random port
8
+ * - Generate valid webhook signatures (GitHub, Trello)
9
+ * - Wait for ingestor state transitions and event arrival
10
+ */
11
+ import crypto from 'node:crypto';
12
+ import path from 'node:path';
13
+ import dotenv from 'dotenv';
14
+ import { expect } from 'vitest';
15
+ import { createApp } from '../../server.js';
16
+ import { generateKeyBundle, extractPublicKeys } from '../../../shared/crypto/index.js';
17
+ import { IngestorManager } from '../manager.js';
18
+ // ── Env loading ─────────────────────────────────────────────────────────
19
+ /** Load .env.e2e from the project root (idempotent). */
20
+ let envLoaded = false;
21
+ export function loadE2EEnv() {
22
+ if (envLoaded)
23
+ return;
24
+ dotenv.config({ path: path.resolve(import.meta.dirname, '../../../../.env.e2e') });
25
+ envLoaded = true;
26
+ }
27
+ /**
28
+ * Check that all required env vars are set.
29
+ * Returns the missing var names (empty array = all present).
30
+ */
31
+ export function checkEnvVars(vars) {
32
+ loadE2EEnv();
33
+ return vars.filter((v) => !process.env[v]);
34
+ }
35
+ // ── Config builder ──────────────────────────────────────────────────────
36
+ /**
37
+ * Build a RemoteServerConfig for E2E tests.
38
+ *
39
+ * Creates a single caller ("e2e-client") with the requested connections.
40
+ * Env vars are injected into caller.env so secret resolution works against
41
+ * process.env (already loaded by loadE2EEnv).
42
+ */
43
+ export function buildE2EConfig(connections, opts) {
44
+ return {
45
+ host: '127.0.0.1',
46
+ port: 0,
47
+ callers: {
48
+ 'e2e-client': {
49
+ connections,
50
+ env: opts?.env,
51
+ ingestorOverrides: opts?.ingestorOverrides,
52
+ },
53
+ },
54
+ rateLimitPerMinute: 600,
55
+ };
56
+ }
57
+ /**
58
+ * Boot an Express server with the given config on a random port.
59
+ * Starts all ingestors and returns handles for testing.
60
+ */
61
+ export async function bootServer(config) {
62
+ const serverKeys = generateKeyBundle();
63
+ const clientKeys = generateKeyBundle();
64
+ const clientPub = extractPublicKeys(clientKeys);
65
+ const ingestorManager = new IngestorManager(config);
66
+ const app = createApp({
67
+ config,
68
+ ownKeys: serverKeys,
69
+ authorizedPeers: [{ alias: 'e2e-client', keys: clientPub }],
70
+ ingestorManager,
71
+ disableRateLimiting: true,
72
+ });
73
+ // Start ingestors
74
+ await ingestorManager.startAll();
75
+ // Listen on random port
76
+ const server = await new Promise((resolve) => {
77
+ const s = app.listen(0, '127.0.0.1', () => resolve(s));
78
+ });
79
+ const addr = server.address();
80
+ const baseUrl = `http://127.0.0.1:${addr.port}`;
81
+ return {
82
+ server,
83
+ baseUrl,
84
+ ingestorManager,
85
+ teardown: async () => {
86
+ await ingestorManager.stopAll();
87
+ await new Promise((resolve, reject) => {
88
+ server.close((err) => (err ? reject(err) : resolve()));
89
+ });
90
+ },
91
+ };
92
+ }
93
+ // ── Signature helpers ───────────────────────────────────────────────────
94
+ /** Generate a valid GitHub HMAC-SHA256 signature for a raw body. */
95
+ export function signGitHubPayload(rawBody, secret) {
96
+ const buf = typeof rawBody === 'string' ? Buffer.from(rawBody) : rawBody;
97
+ const sig = crypto.createHmac('sha256', secret).update(buf).digest('hex');
98
+ return `sha256=${sig}`;
99
+ }
100
+ /** Generate a valid Trello HMAC-SHA1 base64 signature for body + callbackUrl. */
101
+ export function signTrelloPayload(rawBody, callbackUrl, secret) {
102
+ const bodyStr = typeof rawBody === 'string' ? rawBody : rawBody.toString('utf-8');
103
+ return crypto.createHmac('sha1', secret).update(bodyStr + callbackUrl).digest('base64');
104
+ }
105
+ // ── Polling helpers ─────────────────────────────────────────────────────
106
+ /**
107
+ * Wait for an ingestor to reach a specific state.
108
+ * Polls every 250ms until the timeout is reached.
109
+ */
110
+ export async function waitForIngestorState(manager, callerAlias, connection, targetState, timeoutMs = 15_000) {
111
+ const start = Date.now();
112
+ while (Date.now() - start < timeoutMs) {
113
+ const statuses = manager.getStatuses(callerAlias);
114
+ const status = statuses.find((s) => s.connection === connection);
115
+ if (status?.state === targetState)
116
+ return;
117
+ await new Promise((r) => setTimeout(r, 250));
118
+ }
119
+ throw new Error(`Timed out waiting for ${connection} to reach state "${targetState}" (${timeoutMs}ms)`);
120
+ }
121
+ /**
122
+ * Poll until at least one event appears for a connection.
123
+ * Returns the events array once non-empty.
124
+ */
125
+ export async function pollUntilEvent(manager, callerAlias, connection, timeoutMs = 10_000) {
126
+ const start = Date.now();
127
+ while (Date.now() - start < timeoutMs) {
128
+ const events = manager.getEvents(callerAlias, connection);
129
+ if (events.length > 0)
130
+ return events;
131
+ await new Promise((r) => setTimeout(r, 250));
132
+ }
133
+ throw new Error(`Timed out waiting for events from ${connection} (${timeoutMs}ms)`);
134
+ }
135
+ // ── Shared assertions ───────────────────────────────────────────────────
136
+ /** Common shape every IngestedEvent must satisfy (for use with toMatchObject). */
137
+ export const INGESTED_EVENT_SHAPE = {
138
+ id: expect.any(Number),
139
+ idempotencyKey: expect.any(String),
140
+ receivedAt: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T/),
141
+ receivedAtMs: expect.any(Number),
142
+ callerAlias: 'e2e-client',
143
+ source: expect.any(String),
144
+ eventType: expect.any(String),
145
+ data: expect.anything(),
146
+ };
147
+ //# sourceMappingURL=setup.js.map
@@ -1,15 +1,19 @@
1
1
  /**
2
2
  * IngestorManager — owns and manages the lifecycle of all ingestor instances.
3
3
  *
4
- * Keyed by `callerAlias:connectionAlias`, so each caller gets its own
5
- * ingestor instance (with its own secrets, buffer, and connection state).
4
+ * Keyed by `callerAlias:connectionAlias:instanceId`, so each caller gets its
5
+ * own ingestor instance (with its own secrets, buffer, and connection state).
6
6
  * Multiple sessions from the same caller share the same ingestor/buffer.
7
7
  *
8
+ * Supports multi-instance listeners: when a caller defines `listenerInstances`
9
+ * for a connection, one ingestor is spawned per instanceId (e.g., watching
10
+ * multiple Trello boards or multiple Reddit subreddits simultaneously).
11
+ *
8
12
  * The manager is created once when the remote server starts, and provides
9
13
  * event retrieval and status methods used by the `poll_events` and
10
14
  * `ingestor_status` tool handlers.
11
15
  */
12
- import { type IngestorOverrides, type RemoteServerConfig } from '../../shared/config.js';
16
+ import { type IngestorOverrides, type RemoteServerConfig, type ListenerConfigField } from '../../shared/config.js';
13
17
  import type { IngestedEvent, IngestorConfig, IngestorStatus } from './types.js';
14
18
  import { WebhookIngestor } from './webhook/base-webhook-ingestor.js';
15
19
  import './discord/discord-gateway.js';
@@ -18,27 +22,66 @@ import './webhook/github-webhook-ingestor.js';
18
22
  import './webhook/stripe-webhook-ingestor.js';
19
23
  import './webhook/trello-webhook-ingestor.js';
20
24
  import './poll/poll-ingestor.js';
25
+ /** Sentinel instance ID used for single-instance (default) connections. */
26
+ declare const DEFAULT_INSTANCE_ID = "_default";
27
+ /** Build a composite ingestor map key. */
28
+ declare function makeKey(caller: string, connection: string, instance?: string): string;
29
+ /** Parse a composite ingestor map key. */
30
+ declare function parseKey(key: string): {
31
+ caller: string;
32
+ connection: string;
33
+ instance: string;
34
+ };
35
+ interface LifecycleResult {
36
+ success: boolean;
37
+ connection: string;
38
+ instanceId?: string;
39
+ state?: string;
40
+ error?: string;
41
+ }
21
42
  export declare class IngestorManager {
22
43
  private readonly config;
23
- /** Active ingestor instances, keyed by `callerAlias:connectionAlias`. */
44
+ /** Active ingestor instances, keyed by `callerAlias:connectionAlias:instanceId`. */
24
45
  private ingestors;
25
- constructor(config: RemoteServerConfig);
46
+ /** Trigger rule engines per caller. Created during startAll() for callers with triggerRules. */
47
+ private triggerEngines;
48
+ /** Global event listeners (e.g. SSE streams). Called for every event from every ingestor. */
49
+ private eventListeners;
50
+ /**
51
+ * Optional config loader for hot-reload support. When provided, `startOne()`
52
+ * uses it to get fresh config from disk instead of the constructor snapshot.
53
+ */
54
+ private configLoader;
55
+ constructor(config: RemoteServerConfig, configLoader?: () => RemoteServerConfig);
56
+ /** Return fresh config if a loader is available, otherwise the constructor snapshot. */
57
+ private getConfig;
26
58
  /**
27
59
  * Start ingestors for all callers whose connections have an `ingestor` config.
28
60
  * Called once when the remote server starts listening.
61
+ *
62
+ * For connections with `listenerInstances`, spawns one ingestor per instanceId.
63
+ * For connections without, spawns a single default instance (backward compatible).
29
64
  */
30
65
  startAll(): Promise<void>;
66
+ /**
67
+ * Internal: create, register, and start a single ingestor instance.
68
+ */
69
+ private startIngestor;
70
+ /**
71
+ * Initialize trigger rule engines for callers with triggerRules config.
72
+ * Subscribes to 'event' emissions from matching ingestors and dispatches
73
+ * to Claude Code remote triggers.
74
+ */
75
+ private initTriggerEngines;
31
76
  /**
32
77
  * Stop all running ingestors. Called during graceful shutdown.
33
78
  */
34
79
  stopAll(): Promise<void>;
35
80
  /**
36
- * Get events for a specific caller and connection.
37
- * @param callerAlias The caller whose events to retrieve.
38
- * @param connectionAlias The connection to filter by.
39
- * @param afterId Return events with id > afterId. Pass -1 for all.
81
+ * Get events for a specific caller and connection, optionally filtered by instance.
82
+ * When instanceId is omitted, aggregates events from all instances of that connection.
40
83
  */
41
- getEvents(callerAlias: string, connectionAlias: string, afterId?: number): IngestedEvent[];
84
+ getEvents(callerAlias: string, connectionAlias: string, afterId?: number, instanceId?: string): IngestedEvent[];
42
85
  /**
43
86
  * Get events across all ingestors for a caller, sorted chronologically.
44
87
  * @param callerAlias The caller whose events to retrieve.
@@ -49,6 +92,23 @@ export declare class IngestorManager {
49
92
  * Get status of all ingestors for a caller.
50
93
  */
51
94
  getStatuses(callerAlias: string): IngestorStatus[];
95
+ /**
96
+ * Get status of every ingestor across all callers, augmented with the
97
+ * owning caller alias. Used by the read-only /admin API to render a global
98
+ * dashboard view without iterating per-caller.
99
+ */
100
+ getAllStatuses(): (IngestorStatus & {
101
+ callerAlias: string;
102
+ })[];
103
+ /**
104
+ * Subscribe to all events from all ingestors (current and future).
105
+ * Used by the SSE /events/stream endpoint to fan out events to CLI watchers.
106
+ */
107
+ onEvent(listener: (event: IngestedEvent) => void): void;
108
+ /** Unsubscribe a global event listener. */
109
+ offEvent(listener: (event: IngestedEvent) => void): void;
110
+ /** Forward an ingestor event to all global listeners. */
111
+ private notifyEventListeners;
52
112
  /**
53
113
  * Find all webhook ingestor instances that match a given webhook path.
54
114
  * Returns all matching instances across all callers (for fan-out dispatch).
@@ -56,10 +116,50 @@ export declare class IngestorManager {
56
116
  * @param path - The webhook path segment (e.g., 'github' from /webhooks/github).
57
117
  */
58
118
  getWebhookIngestors(path: string): WebhookIngestor[];
119
+ /**
120
+ * Start a single ingestor for a specific caller+connection pair.
121
+ * When instanceId is provided, starts that specific instance.
122
+ * When omitted, starts the default instance (or all instances if listenerInstances is defined).
123
+ */
124
+ startOne(callerAlias: string, connectionAlias: string, instanceId?: string): Promise<LifecycleResult | LifecycleResult[]>;
125
+ /** Internal: start a single specific instance. */
126
+ private startOneInstance;
127
+ /**
128
+ * Stop a single ingestor for a specific caller+connection pair.
129
+ * When instanceId is provided, stops that specific instance.
130
+ * When omitted, stops all instances of that connection.
131
+ */
132
+ stopOne(callerAlias: string, connectionAlias: string, instanceId?: string, options?: {
133
+ permanent?: boolean;
134
+ }): Promise<LifecycleResult | LifecycleResult[]>;
135
+ /** Internal: stop a single specific instance. */
136
+ private stopOneInstance;
137
+ /**
138
+ * Restart a single ingestor (stop + start). Useful after configuration changes.
139
+ */
140
+ restartOne(callerAlias: string, connectionAlias: string, instanceId?: string): Promise<LifecycleResult | LifecycleResult[]>;
141
+ /**
142
+ * Check if an ingestor exists (running or otherwise) for a caller+connection pair.
143
+ * When instanceId is provided, checks that specific instance.
144
+ * When omitted, checks if any instance exists for that connection.
145
+ */
146
+ has(callerAlias: string, connectionAlias: string, instanceId?: string): boolean;
59
147
  /**
60
148
  * Merge caller-level ingestor overrides into a copy of the template config.
61
149
  * Override fields replace template values; omitted fields inherit the template defaults.
62
150
  */
63
151
  static mergeIngestorConfig(templateConfig: IngestorConfig, overrides?: IngestorOverrides): IngestorConfig;
152
+ /**
153
+ * Apply instance-specific params from IngestorOverrides to the effective config and secrets.
154
+ *
155
+ * For each param, checks the connection's listenerConfig fields:
156
+ * - If the field has `overrideKey`, injects `params[key]` as `secrets[overrideKey]`
157
+ * (used by poll ingestors for URL template resolution, e.g., REDDIT_SUBREDDIT).
158
+ * - If the field has `instanceKey` and the config is a webhook, attaches
159
+ * the value to the webhook config for payload discrimination (e.g., _boardId for Trello).
160
+ * - Otherwise, attaches the value as a generic `_instanceParams` bag on the config.
161
+ */
162
+ static applyInstanceParams(config: IngestorConfig, secrets: Record<string, string>, params: Record<string, unknown>, fields: ListenerConfigField[]): void;
64
163
  }
164
+ export { makeKey, parseKey, DEFAULT_INSTANCE_ID };
65
165
  //# sourceMappingURL=manager.d.ts.map