@interactive-inc/claude-funnel 0.26.0 → 0.27.0

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 (27) hide show
  1. package/dist/bin.js +786 -717
  2. package/dist/connector-diagnostic-log-Clb2sCcz.d.ts +206 -0
  3. package/dist/connectors/discord.d.ts +16 -6
  4. package/dist/connectors/discord.js +1 -1
  5. package/dist/connectors/gh.d.ts +12 -5
  6. package/dist/connectors/gh.js +1 -1
  7. package/dist/connectors/schedule.d.ts +1 -1
  8. package/dist/connectors/schedule.js +1 -1
  9. package/dist/connectors/slack.d.ts +5 -4
  10. package/dist/connectors/slack.js +1 -1
  11. package/dist/{discord-connector-schema-Dww2I4zH.d.ts → discord-connector-schema-Df_McRJI.d.ts} +7 -1
  12. package/dist/{discord-connector-schema-CpuI6rmE.js → discord-connector-schema-RzDvrNE5.js} +81 -8
  13. package/dist/gateway/daemon.js +225 -225
  14. package/dist/{gh-connector-schema-CQRIvPpz.js → gh-connector-schema-eYE4g77K.js} +51 -3
  15. package/dist/index.d.ts +213 -38
  16. package/dist/index.js +727 -103
  17. package/dist/resolve-connector-token-Ch6XWMJM.js +22 -0
  18. package/dist/{schedule-connector-schema-CuCjP7z4.js → schedule-connector-schema-CM-sRkac.js} +53 -3
  19. package/dist/{schedule-listener-CBYF2bGZ.d.ts → schedule-listener-C2-KqHQc.d.ts} +10 -3
  20. package/dist/{slack-connector-schema-BWL7dWlY.js → slack-connector-schema-CHbRJHGp.js} +140 -19
  21. package/dist/slack-listener-BMknoyVr.d.ts +112 -0
  22. package/package.json +1 -1
  23. package/dist/logger-B3aXsVcX.d.ts +0 -33
  24. package/dist/slack-listener-DbNCPMqY.d.ts +0 -77
  25. /package/dist/{connector-adapter-CXB-q_XC.d.ts → connector-adapter-VA6undzc.d.ts} +0 -0
  26. /package/dist/{gh-connector-schema-Cmi57jvL.d.ts → gh-connector-schema-CQmEWzdV.d.ts} +0 -0
  27. /package/dist/{logger-D1A3_JXV.js → logger-Czli2OKh.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
2
- import { n as FunnelConnectorListener } from "./logger-D1A3_JXV.js";
2
+ import { n as FunnelConnectorListener } from "./logger-Czli2OKh.js";
3
3
  import { openSync } from "node:fs";
4
4
  import { z } from "zod";
5
5
  //#region lib/engine/process/process-runner.ts
@@ -267,8 +267,10 @@ const MAX_SEEN = 1e4;
267
267
  const KEEP_SEEN = 5e3;
268
268
  var FunnelGhListener = class extends FunnelConnectorListener {
269
269
  config;
270
+ channelId;
270
271
  process;
271
272
  logger;
273
+ diagnosticLog;
272
274
  now;
273
275
  seen = /* @__PURE__ */ new Map();
274
276
  bootstrapped = false;
@@ -277,12 +279,15 @@ var FunnelGhListener = class extends FunnelConnectorListener {
277
279
  constructor(deps) {
278
280
  super();
279
281
  this.config = deps.config;
282
+ this.channelId = deps.channelId ?? null;
280
283
  this.process = deps.process ?? defaultProcess;
281
284
  this.logger = deps.logger;
285
+ this.diagnosticLog = deps.diagnosticLog;
282
286
  this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
283
287
  this.since = this.now().toISOString();
284
288
  }
285
289
  async start(notify) {
290
+ this.recordConnection("started", "");
286
291
  await this.pollOnce(notify);
287
292
  const interval = this.config.pollInterval ?? 60;
288
293
  this.timer = setInterval(() => void this.pollOnce(notify), interval * 1e3);
@@ -292,6 +297,7 @@ var FunnelGhListener = class extends FunnelConnectorListener {
292
297
  if (!this.timer) return;
293
298
  clearInterval(this.timer);
294
299
  this.timer = null;
300
+ this.recordConnection("stopped", "");
295
301
  }
296
302
  isAlive() {
297
303
  return this.timer !== null;
@@ -309,19 +315,26 @@ var FunnelGhListener = class extends FunnelConnectorListener {
309
315
  `/notifications?${params}`
310
316
  ]);
311
317
  if (result.exitCode !== 0) {
318
+ this.recordConnection("error", `gh api exited ${result.exitCode}: ${result.stderr}`);
312
319
  this.logger?.error("gh poll failed", { stderr: result.stderr });
313
320
  return;
314
321
  }
315
322
  const parsed = ghNotificationsSchema.safeParse(JSON.parse(result.stdout));
316
323
  if (!parsed.success) {
324
+ this.recordConnection("error", `gh response schema mismatch: ${parsed.error.message}`);
317
325
  this.logger?.warn("gh response did not match schema", { error: parsed.error.message });
318
326
  return;
319
327
  }
328
+ if (!this.bootstrapped) this.recordConnection("connected", "");
320
329
  const items = parsed.data;
321
330
  for (const item of items) {
322
331
  if (this.seen.get(item.id) === item.updated_at) continue;
323
332
  this.seen.set(item.id, item.updated_at);
324
- if (!this.bootstrapped) continue;
333
+ this.recordRaw(item.id, item);
334
+ if (!this.bootstrapped) {
335
+ this.recordProcessed(item.id, item, "skip:bootstrap", "");
336
+ continue;
337
+ }
325
338
  const meta = {
326
339
  event_type: "gh",
327
340
  reason: item.reason,
@@ -331,7 +344,14 @@ var FunnelGhListener = class extends FunnelConnectorListener {
331
344
  thread_id: item.id,
332
345
  updated_at: item.updated_at
333
346
  };
334
- await notify(JSON.stringify(item), meta);
347
+ const content = JSON.stringify(item);
348
+ try {
349
+ await notify(content, meta);
350
+ } catch (error) {
351
+ this.recordProcessed(item.id, item, "emitted:delivery-failed", content);
352
+ throw error;
353
+ }
354
+ this.recordProcessed(item.id, item, "emitted", content);
335
355
  }
336
356
  if (this.seen.size > MAX_SEEN) {
337
357
  const toDrop = this.seen.size - KEEP_SEEN;
@@ -348,6 +368,34 @@ var FunnelGhListener = class extends FunnelConnectorListener {
348
368
  this.logger?.error("gh poll error", { error: error instanceof Error ? error.message : String(error) });
349
369
  }
350
370
  }
371
+ recordRaw(eventId, item) {
372
+ this.diagnosticLog?.recordRaw({
373
+ eventId,
374
+ type: "gh",
375
+ connectorId: this.config.id,
376
+ channelId: this.channelId,
377
+ payload: JSON.stringify(item)
378
+ });
379
+ }
380
+ recordProcessed(eventId, item, outcome, content) {
381
+ this.diagnosticLog?.recordProcessed({
382
+ eventId,
383
+ type: "gh",
384
+ connectorId: this.config.id,
385
+ channelId: this.channelId,
386
+ outcome,
387
+ payload: content || JSON.stringify(item)
388
+ });
389
+ }
390
+ recordConnection(status, detail) {
391
+ this.diagnosticLog?.recordConnection({
392
+ type: "gh",
393
+ connectorId: this.config.id,
394
+ channelId: this.channelId,
395
+ status,
396
+ detail
397
+ });
398
+ }
351
399
  };
352
400
  //#endregion
353
401
  //#region lib/connectors/gh-connector-schema.ts
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { n as FunnelConnectorAdapter, t as CallInput } from "./connector-adapter-CXB-q_XC.js";
2
- import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "./discord-connector-schema-Dww2I4zH.js";
3
- import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-B3aXsVcX.js";
4
- import { a as FunnelProcessRunner, c as RunResult, i as DetachOptions, n as ghConnectorSchema, o as ProcessSnapshot, r as AttachOptions, s as RunOptions, t as GhConnectorConfig } from "./gh-connector-schema-Cmi57jvL.js";
5
- import { a as FunnelFileSystem, c as ScheduleEntry, d as scheduleEntrySchema, i as FileStat, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, s as ScheduleConnectorConfig, u as scheduleConnectorSchema } from "./schedule-listener-CBYF2bGZ.js";
6
- import { a as SlackProcessed, c as SlackRawEvent, i as FunnelSlackEventProcessor, l as SlackConnectorConfig, n as SlackOnAppCreated, o as SlackProcessedEmit, r as SlackPreprocessEvent, s as SlackProcessedSkip, u as slackConnectorSchema } from "./slack-listener-DbNCPMqY.js";
1
+ import { n as FunnelConnectorAdapter, t as CallInput } from "./connector-adapter-VA6undzc.js";
2
+ import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "./discord-connector-schema-Df_McRJI.js";
3
+ import { S as NotifyFn, _ as connectorConnectionEventSchema, a as ConnectorConnectionStatus, b as FunnelLogger, c as ConnectorProcessedQuery, d as ConnectorRawEvent, f as ConnectorRawQuery, g as StoredRawEvent, h as StoredProcessedEvent, i as ConnectorConnectionRecord, l as ConnectorProcessedRecord, m as StoredConnectionEvent, n as ConnectorConnectionEvent, o as ConnectorDiagnosticLog, p as ConnectorRawRecord, r as ConnectorConnectionQuery, s as ConnectorProcessedEvent, t as CONNECTOR_CONNECTION_STATUSES, u as ConnectorQuery, v as connectorProcessedEventSchema, x as FunnelConnectorListener, y as connectorRawEventSchema } from "./connector-diagnostic-log-Clb2sCcz.js";
4
+ import { a as FunnelProcessRunner, c as RunResult, i as DetachOptions, n as ghConnectorSchema, o as ProcessSnapshot, r as AttachOptions, s as RunOptions, t as GhConnectorConfig } from "./gh-connector-schema-CQmEWzdV.js";
5
+ import { a as FunnelFileSystem, c as ScheduleEntry, d as scheduleEntrySchema, i as FileStat, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, s as ScheduleConnectorConfig, u as scheduleConnectorSchema } from "./schedule-listener-C2-KqHQc.js";
6
+ import { a as SlackProcessed, c as SlackRawEvent, d as slackConnectorSchema, i as FunnelSlackEventProcessor, l as SlackSkipReason, n as SlackOnAppCreated, o as SlackProcessedEmit, r as SlackPreprocessEvent, s as SlackProcessedSkip, u as SlackConnectorConfig } from "./slack-listener-BMknoyVr.js";
7
7
  import { z } from "zod";
8
8
  import * as _$hono_factory0 from "hono/factory";
9
9
  import { Hono } from "hono";
@@ -16,8 +16,10 @@ declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
16
16
  id: z.ZodString;
17
17
  name: z.ZodString;
18
18
  type: z.ZodLiteral<"slack">;
19
- botToken: z.ZodString;
20
- appToken: z.ZodString;
19
+ botToken: z.ZodOptional<z.ZodString>;
20
+ appToken: z.ZodOptional<z.ZodString>;
21
+ botTokenEnv: z.ZodOptional<z.ZodString>;
22
+ appTokenEnv: z.ZodOptional<z.ZodString>;
21
23
  minify: z.ZodDefault<z.ZodBoolean>;
22
24
  createdAt: z.ZodOptional<z.ZodString>;
23
25
  updatedAt: z.ZodOptional<z.ZodString>;
@@ -32,7 +34,8 @@ declare const connectorConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
32
34
  id: z.ZodString;
33
35
  name: z.ZodString;
34
36
  type: z.ZodLiteral<"discord">;
35
- botToken: z.ZodString;
37
+ botToken: z.ZodOptional<z.ZodString>;
38
+ botTokenEnv: z.ZodOptional<z.ZodString>;
36
39
  createdAt: z.ZodOptional<z.ZodString>;
37
40
  updatedAt: z.ZodOptional<z.ZodString>;
38
41
  }, z.core.$strip>, z.ZodObject<{
@@ -68,7 +71,8 @@ type Deps$15 = {
68
71
  fs?: FunnelFileSystem;
69
72
  process?: FunnelProcessRunner;
70
73
  logger?: FunnelLogger;
71
- dir?: string; /** Per-listener hooks for the slack connector type. Threaded into every Slack listener built by this factory. */
74
+ dir?: string; /** Diagnostic log of inbound connector traffic. Threaded into listeners that record raw/processed events. No-op when absent. */
75
+ diagnosticLog?: ConnectorDiagnosticLog; /** Per-listener hooks for the slack connector type. Threaded into every Slack listener built by this factory. */
72
76
  slackListenerOptions?: SlackListenerOptions; /** Per-listener hooks for the schedule connector type. Threaded into every Schedule listener built by this factory. */
73
77
  scheduleListenerOptions?: ScheduleListenerOptions;
74
78
  };
@@ -88,6 +92,7 @@ declare class FunnelConnectorFactory {
88
92
  private readonly fs;
89
93
  private readonly process;
90
94
  private readonly logger;
95
+ private readonly diagnosticLog;
91
96
  private readonly dir;
92
97
  private readonly slackListenerOptions;
93
98
  private readonly scheduleListenerOptions;
@@ -155,8 +160,10 @@ declare const channelConfigSchema: z.ZodObject<{
155
160
  id: z.ZodString;
156
161
  name: z.ZodString;
157
162
  type: z.ZodLiteral<"slack">;
158
- botToken: z.ZodString;
159
- appToken: z.ZodString;
163
+ botToken: z.ZodOptional<z.ZodString>;
164
+ appToken: z.ZodOptional<z.ZodString>;
165
+ botTokenEnv: z.ZodOptional<z.ZodString>;
166
+ appTokenEnv: z.ZodOptional<z.ZodString>;
160
167
  minify: z.ZodDefault<z.ZodBoolean>;
161
168
  createdAt: z.ZodOptional<z.ZodString>;
162
169
  updatedAt: z.ZodOptional<z.ZodString>;
@@ -171,7 +178,8 @@ declare const channelConfigSchema: z.ZodObject<{
171
178
  id: z.ZodString;
172
179
  name: z.ZodString;
173
180
  type: z.ZodLiteral<"discord">;
174
- botToken: z.ZodString;
181
+ botToken: z.ZodOptional<z.ZodString>;
182
+ botTokenEnv: z.ZodOptional<z.ZodString>;
175
183
  createdAt: z.ZodOptional<z.ZodString>;
176
184
  updatedAt: z.ZodOptional<z.ZodString>;
177
185
  }, z.core.$strip>, z.ZodObject<{
@@ -219,8 +227,10 @@ declare const settingsSchema: z.ZodObject<{
219
227
  id: z.ZodString;
220
228
  name: z.ZodString;
221
229
  type: z.ZodLiteral<"slack">;
222
- botToken: z.ZodString;
223
- appToken: z.ZodString;
230
+ botToken: z.ZodOptional<z.ZodString>;
231
+ appToken: z.ZodOptional<z.ZodString>;
232
+ botTokenEnv: z.ZodOptional<z.ZodString>;
233
+ appTokenEnv: z.ZodOptional<z.ZodString>;
224
234
  minify: z.ZodDefault<z.ZodBoolean>;
225
235
  createdAt: z.ZodOptional<z.ZodString>;
226
236
  updatedAt: z.ZodOptional<z.ZodString>;
@@ -235,7 +245,8 @@ declare const settingsSchema: z.ZodObject<{
235
245
  id: z.ZodString;
236
246
  name: z.ZodString;
237
247
  type: z.ZodLiteral<"discord">;
238
- botToken: z.ZodString;
248
+ botToken: z.ZodOptional<z.ZodString>;
249
+ botTokenEnv: z.ZodOptional<z.ZodString>;
239
250
  createdAt: z.ZodOptional<z.ZodString>;
240
251
  updatedAt: z.ZodOptional<z.ZodString>;
241
252
  }, z.core.$strip>, z.ZodObject<{
@@ -291,8 +302,10 @@ type ChannelConnectorView = ConnectorConfig & {
291
302
  type AddConnectorInput = {
292
303
  type: "slack";
293
304
  name: string;
294
- botToken: string;
295
- appToken: string;
305
+ botToken?: string;
306
+ appToken?: string;
307
+ botTokenEnv?: string;
308
+ appTokenEnv?: string;
296
309
  minify?: boolean;
297
310
  } | {
298
311
  type: "gh";
@@ -301,7 +314,8 @@ type AddConnectorInput = {
301
314
  } | {
302
315
  type: "discord";
303
316
  name: string;
304
- botToken: string;
317
+ botToken?: string;
318
+ botTokenEnv?: string;
305
319
  } | {
306
320
  type: "schedule";
307
321
  name: string;
@@ -342,12 +356,15 @@ declare class FunnelChannels {
342
356
  updateSlackConnector(channelName: string, connectorName: string, fields: {
343
357
  botToken?: string;
344
358
  appToken?: string;
359
+ botTokenEnv?: string;
360
+ appTokenEnv?: string;
345
361
  }): void;
346
362
  updateGhConnector(channelName: string, connectorName: string, fields: {
347
363
  pollInterval?: number;
348
364
  }): void;
349
365
  updateDiscordConnector(channelName: string, connectorName: string, fields: {
350
366
  botToken?: string;
367
+ botTokenEnv?: string;
351
368
  }): void;
352
369
  listScheduleEntries(channelName: string, connectorName: string): ScheduleEntry[];
353
370
  addScheduleEntry(channelName: string, connectorName: string, entry: Pick<ScheduleEntry, "cron" | "prompt"> & Partial<Pick<ScheduleEntry, "id" | "enabled" | "catchupPolicy">>): ScheduleEntry;
@@ -365,6 +382,7 @@ declare class FunnelChannels {
365
382
  listener: FunnelConnectorListener;
366
383
  }[];
367
384
  private requireChannel;
385
+ private replaceConnector;
368
386
  private assertNoTokenCollision;
369
387
  }
370
388
  //#endregion
@@ -748,10 +766,20 @@ declare class FunnelLocalConfigSync {
748
766
  private ensureSchedule;
749
767
  private findExistingSlack;
750
768
  private findExistingDiscord;
751
- private findSlackByToken;
752
- private findDiscordByToken;
753
769
  private removeExtras;
754
- private resolveField;
770
+ /**
771
+ * Decides how a single token slot is stored in settings.json:
772
+ *
773
+ * - `env.<field>` reference → `{ tokenEnv: "<VAR>" }`; the secret is NOT
774
+ * resolved into settings, it stays in the environment / `.env.local` and
775
+ * the listener resolves it at start. We still assert the var is set so a
776
+ * typo fails loudly here instead of as a dead listener later.
777
+ * - literal → `{ token: "<secret>" }`.
778
+ * - neither, but a prior value exists → carry it over verbatim (whichever
779
+ * form it already was), so a tokenless re-sync is a no-op.
780
+ * - nothing at all → prompt for a literal (TTY only; throws otherwise).
781
+ */
782
+ private resolveSlot;
755
783
  }
756
784
  //#endregion
757
785
  //#region lib/gateway/publish-schema.d.ts
@@ -1324,7 +1352,7 @@ declare class FunnelListenersClient {
1324
1352
  }
1325
1353
  //#endregion
1326
1354
  //#region lib/funnel.d.ts
1327
- type Props$6 = {
1355
+ type Props$8 = {
1328
1356
  /** Settings persistence (channels with nested connectors / profiles). Defaults to a FunnelSettingsStore rooted at `dir`. */store?: FunnelSettingsReader; /** Filesystem boundary. Replace with MemoryFunnelFileSystem to sandbox all disk I/O. */
1329
1357
  fs?: FunnelFileSystem; /** Process runner used by gateway / claude / gh listener. Replace with MemoryFunnelProcessRunner for tests. */
1330
1358
  process?: FunnelProcessRunner; /** Logger flowed into every facet. Replace with MemoryFunnelLogger or NoopFunnelLogger to silence/inspect. */
@@ -1345,6 +1373,13 @@ type Props$6 = {
1345
1373
  * each successful fire, useful for dropping one-shot entries.
1346
1374
  */
1347
1375
  scheduleListenerOptions?: ScheduleListenerOptions;
1376
+ /**
1377
+ * Diagnostic log of inbound connector traffic (raw events before filtering
1378
+ * and the processor's verdict after). Threaded into listeners that record
1379
+ * it. Only the gateway daemon injects a `SqliteConnectorDiagnosticLog`; everywhere
1380
+ * else this stays absent and recording is a no-op.
1381
+ */
1382
+ diagnosticLog?: ConnectorDiagnosticLog;
1348
1383
  /**
1349
1384
  * Called when Funnel catches an exception that would otherwise be silently
1350
1385
  * swallowed (subscriber throw, listener start/stop failure, etc.). Pass
@@ -1374,13 +1409,13 @@ type Props$6 = {
1374
1409
  declare class Funnel {
1375
1410
  private readonly props;
1376
1411
  private readonly memos;
1377
- constructor(props?: Props$6);
1412
+ constructor(props?: Props$8);
1378
1413
  /**
1379
1414
  * Sandboxed Funnel wired with in-memory implementations for every IO boundary.
1380
1415
  * Touches no real disk, processes, wall-clock time, or UUIDs — safe for tests
1381
1416
  * and ad-hoc experiments. Override individual fields by passing them in `props`.
1382
1417
  */
1383
- static inMemory(props?: Props$6): Funnel;
1418
+ static inMemory(props?: Props$8): Funnel;
1384
1419
  /** Resolved on-disk paths the facade will read/write when methods are called. Pure compute, not memoized. */
1385
1420
  get paths(): {
1386
1421
  dir: string;
@@ -1478,6 +1513,14 @@ declare const startChannelServer: (options?: ChannelServerOptions) => Promise<vo
1478
1513
  declare const funnelJsonSchema: () => Record<string, unknown>;
1479
1514
  //#endregion
1480
1515
  //#region lib/engine/settings/settings-store.d.ts
1516
+ /**
1517
+ * Resolves the funnel home dir. Defaults to `~/.funnel`, overridable via
1518
+ * `FUNNEL_DIR` so a funnel.json-scoped launch can point everything (settings,
1519
+ * gateway pid/token, claude pids) at a repo-local `<repo>/.funnel` and never
1520
+ * touch the global home. Read at call time, not module load, so a daemon
1521
+ * spawned with the env set resolves the override.
1522
+ */
1523
+ declare function resolveFunnelDir(): string;
1481
1524
  declare const FUNNEL_DIR: string;
1482
1525
  declare const SETTINGS_PATH: string;
1483
1526
  type Deps = {
@@ -1529,7 +1572,7 @@ declare class NodeFunnelFileSystem extends FunnelFileSystem {
1529
1572
  }
1530
1573
  //#endregion
1531
1574
  //#region lib/engine/fs/memory-file-system.d.ts
1532
- type Props$5 = {
1575
+ type Props$7 = {
1533
1576
  dirs?: string[];
1534
1577
  files?: Record<string, string>;
1535
1578
  mtimes?: Record<string, number>;
@@ -1542,7 +1585,7 @@ declare class MemoryFunnelFileSystem extends FunnelFileSystem {
1542
1585
  private readonly mtimes;
1543
1586
  private readonly modes;
1544
1587
  private readonly now;
1545
- constructor(props?: Props$5);
1588
+ constructor(props?: Props$7);
1546
1589
  existsSync(path: string): boolean;
1547
1590
  readFileSync(path: string): string;
1548
1591
  writeFileSync(path: string, data: string): void;
@@ -1628,14 +1671,14 @@ declare class MemoryFunnelProcessRunner extends FunnelProcessRunner {
1628
1671
  }
1629
1672
  //#endregion
1630
1673
  //#region lib/engine/logger/node-logger.d.ts
1631
- type Props$4 = {
1674
+ type Props$6 = {
1632
1675
  file?: string;
1633
1676
  now?: () => Date;
1634
1677
  };
1635
1678
  declare class NodeFunnelLogger extends FunnelLogger {
1636
1679
  readonly file: string;
1637
1680
  private readonly now;
1638
- constructor(props?: Props$4);
1681
+ constructor(props?: Props$6);
1639
1682
  info(message: string, meta?: Record<string, unknown>): void;
1640
1683
  warn(message: string, meta?: Record<string, unknown>): void;
1641
1684
  error(message: string, meta?: Record<string, unknown>): void;
@@ -1671,12 +1714,12 @@ declare class NodeFunnelClock extends FunnelClock {
1671
1714
  }
1672
1715
  //#endregion
1673
1716
  //#region lib/engine/time/memory-clock.d.ts
1674
- type Props$3 = {
1717
+ type Props$5 = {
1675
1718
  start?: Date;
1676
1719
  };
1677
1720
  declare class MemoryFunnelClock extends FunnelClock {
1678
1721
  private current;
1679
- constructor(props?: Props$3);
1722
+ constructor(props?: Props$5);
1680
1723
  now(): Date;
1681
1724
  set(date: Date): void;
1682
1725
  advance(ms: number): void;
@@ -1688,13 +1731,13 @@ declare class NodeFunnelIdGenerator extends FunnelIdGenerator {
1688
1731
  }
1689
1732
  //#endregion
1690
1733
  //#region lib/engine/id/memory-id-generator.d.ts
1691
- type Props$2 = {
1734
+ type Props$4 = {
1692
1735
  prefix?: string;
1693
1736
  };
1694
1737
  declare class MemoryFunnelIdGenerator extends FunnelIdGenerator {
1695
1738
  private counter;
1696
1739
  private readonly prefix;
1697
- constructor(props?: Props$2);
1740
+ constructor(props?: Props$4);
1698
1741
  generate(): string;
1699
1742
  }
1700
1743
  //#endregion
@@ -1711,7 +1754,7 @@ declare class NodeFunnelTokenPrompter extends FunnelTokenPrompter {
1711
1754
  }
1712
1755
  //#endregion
1713
1756
  //#region lib/engine/token-prompter/memory-token-prompter.d.ts
1714
- type Props$1 = {
1757
+ type Props$3 = {
1715
1758
  answers?: Record<string, string>;
1716
1759
  };
1717
1760
  /**
@@ -1721,12 +1764,12 @@ type Props$1 = {
1721
1764
  declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
1722
1765
  private readonly answers;
1723
1766
  readonly asked: string[];
1724
- constructor(props?: Props$1);
1767
+ constructor(props?: Props$3);
1725
1768
  promptSecret(label: string): Promise<string>;
1726
1769
  }
1727
1770
  //#endregion
1728
1771
  //#region lib/gateway/sqlite-funnel-event-log.d.ts
1729
- type Props = {
1772
+ type Props$2 = {
1730
1773
  /** SQLite database file path. Created on first write. ":memory:" for tests. */path: string; /** Override for tests. Defaults to `Date.now`. */
1731
1774
  now?: () => number; /** Optional row cap. Pruned on every insert. */
1732
1775
  maxRows?: number; /** Optional age cap in ms. Pruned on every insert. */
@@ -1752,7 +1795,7 @@ type Props = {
1752
1795
  declare class SqliteFunnelEventLog extends FunnelEventLog {
1753
1796
  private readonly sink;
1754
1797
  private readonly now;
1755
- constructor(props: Props);
1798
+ constructor(props: Props$2);
1756
1799
  /**
1757
1800
  * Persist a broadcaster-driven event with its assigned offset. Caller
1758
1801
  * (the gateway-server) supplies the offset from `broadcaster.broadcast()`
@@ -1797,6 +1840,112 @@ declare class MemoryFunnelEventLog extends FunnelEventLog {
1797
1840
  close(): void;
1798
1841
  }
1799
1842
  //#endregion
1843
+ //#region lib/gateway/sqlite-connector-diagnostic-log.d.ts
1844
+ type Props$1 = {
1845
+ /** SQLite file for the raw (pre-filter) table. ":memory:" for tests. */rawPath: string; /** SQLite file for the processed (verdict) table. ":memory:" for tests. */
1846
+ processedPath: string; /** SQLite file for the connection (lifecycle) table. ":memory:" for tests. */
1847
+ connectionPath: string;
1848
+ now?: () => number; /** Row cap for the processed and connection tables. Pruned on every insert. */
1849
+ maxRows?: number;
1850
+ /**
1851
+ * Row cap for the raw table specifically. Raw rows can each hold up to
1852
+ * `RAW_PAYLOAD_CAP` bytes, so they want a tighter cap than the small
1853
+ * processed/connection verdict rows. Defaults to `maxRows` when unset.
1854
+ */
1855
+ rawMaxRows?: number; /** Age cap in ms for all tables — bounds how long untouched payloads (with PII) live. Pruned on every insert. */
1856
+ maxAgeMs?: number; /** When set, `insert()` errors (disk full, WAL lock) are logged instead of silently dropped. */
1857
+ logger?: FunnelLogger;
1858
+ };
1859
+ /**
1860
+ * Default `ConnectorDiagnosticLog`: three independent `LeucoLoggerSqliteSink`s, one
1861
+ * per table (raw / processed / connection), in separate files. Each sink
1862
+ * indexes the columns its queries filter on — `event_id` / `connector_id` /
1863
+ * `channel_id` for raw, plus `outcome` for processed and `status` for
1864
+ * connection — so those lookups are indexed scans (`type` is a fixed column
1865
+ * the sink extracts separately, not an index, so filtering by it is a scan).
1866
+ *
1867
+ * The raw table offloads any payload over `RAW_PAYLOAD_CAP`: rather than
1868
+ * truncating mid-string (which yields unparseable JSON), it replaces the
1869
+ * body with a small JSON object that keeps the diagnostic essentials and
1870
+ * records the dropped size under `_funnel_oversized`. Every stored payload
1871
+ * therefore stays valid JSON.
1872
+ */
1873
+ declare class SqliteConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1874
+ private readonly raw;
1875
+ private readonly processed;
1876
+ private readonly connection;
1877
+ private readonly now;
1878
+ private readonly logger;
1879
+ constructor(props: Props$1);
1880
+ recordRaw(record: ConnectorRawRecord): void;
1881
+ recordProcessed(record: ConnectorProcessedRecord): void;
1882
+ recordConnection(record: ConnectorConnectionRecord): void;
1883
+ private report;
1884
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1885
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1886
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1887
+ close(): void;
1888
+ }
1889
+ //#endregion
1890
+ //#region lib/gateway/memory-connector-diagnostic-log.d.ts
1891
+ /**
1892
+ * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
1893
+ * and embedders that do not need durability. Like the SQLite log it keeps
1894
+ * `seq` per-table (each array's 1-based position) and returns the most recent
1895
+ * `limit` rows oldest-first; unlike it, it never prunes and never offloads
1896
+ * oversized payloads — it keeps whatever the caller hands it, which is fine
1897
+ * for the bounded volumes a test produces. Payload-validity is therefore a
1898
+ * SQLite-only guarantee; do not write a test that leans on this double
1899
+ * rejecting a malformed payload.
1900
+ */
1901
+ declare class MemoryConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1902
+ private readonly now;
1903
+ private readonly raws;
1904
+ private readonly processeds;
1905
+ private readonly connections;
1906
+ constructor(now?: () => number);
1907
+ recordRaw(record: ConnectorRawRecord): void;
1908
+ recordProcessed(record: ConnectorProcessedRecord): void;
1909
+ recordConnection(record: ConnectorConnectionRecord): void;
1910
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1911
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1912
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1913
+ close(): void;
1914
+ }
1915
+ //#endregion
1916
+ //#region lib/gateway/connector-diagnostic-sql-reader.d.ts
1917
+ type Props = {
1918
+ /** SQLite file holding the raw (pre-filter) table. */rawPath: string; /** SQLite file holding the processed (verdict) table. */
1919
+ processedPath: string; /** SQLite file holding the connection (lifecycle) table. */
1920
+ connectionPath: string;
1921
+ };
1922
+ type Row = Record<string, unknown>;
1923
+ /**
1924
+ * Read-only SQL surface over the three diagnostic tables, for Claude to query
1925
+ * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
1926
+ * three views — `raw`, `processed`, `connection` — that hide the storage
1927
+ * details (the physical table is `leuco_log` and each row's columns live
1928
+ * inside a JSON `event` blob): the views surface the columns as plain fields,
1929
+ * with `payload` already pulled out of the nested JSON.
1930
+ *
1931
+ * The tables are separate files. `raw` and `processed` share an `event_id`,
1932
+ * so a `JOIN` answers "the event arrived, but what verdict did it get?";
1933
+ * `connection` answers the other half — "did the listener ever connect at
1934
+ * all?". Writes are impossible: the connection is read-only and `query`
1935
+ * rejects anything but a single `SELECT`.
1936
+ */
1937
+ declare class ConnectorDiagnosticSqlReader {
1938
+ private readonly db;
1939
+ constructor(props: Props);
1940
+ /**
1941
+ * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
1942
+ * than throwing) for a non-SELECT statement or a SQL error, so the caller
1943
+ * can surface the message without a stack trace.
1944
+ */
1945
+ query(sql: string): Row[] | Error;
1946
+ close(): void;
1947
+ }
1948
+ //#endregion
1800
1949
  //#region lib/cli/factory.d.ts
1801
1950
  type Env = {
1802
1951
  Variables: {
@@ -3051,6 +3200,19 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
3051
3200
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3052
3201
  };
3053
3202
  };
3203
+ } & {
3204
+ "/gateway/sql": {
3205
+ $get: {
3206
+ input: {
3207
+ query: {
3208
+ query?: string | undefined;
3209
+ };
3210
+ };
3211
+ output: string;
3212
+ outputFormat: "text";
3213
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3214
+ };
3215
+ };
3054
3216
  } & {
3055
3217
  "/gateway/listeners": {
3056
3218
  $get: {
@@ -4352,6 +4514,19 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4352
4514
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4353
4515
  };
4354
4516
  };
4517
+ } & {
4518
+ "/gateway/sql": {
4519
+ $get: {
4520
+ input: {
4521
+ query: {
4522
+ query?: string | undefined;
4523
+ };
4524
+ };
4525
+ output: string;
4526
+ outputFormat: "text";
4527
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
4528
+ };
4529
+ };
4355
4530
  } & {
4356
4531
  "/gateway/listeners": {
4357
4532
  $get: {
@@ -4431,4 +4606,4 @@ ${string}`;
4431
4606
  //#region lib/tui/tui.d.ts
4432
4607
  declare function launchTui(funnel: Funnel): Promise<void>;
4433
4608
  //#endregion
4434
- export { AliveStub, AttachOptions, BroadcastEvent, BroadcastSubscriber, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ChannelSpec, ConnectorConfig, ConnectorSpec, ConnectorSyncOutcome, ConnectorType, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEvent, FunnelEventLog, FunnelEventRecord, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, type GatewayEmitInput, type GatewayRouteDeps, type Env$1 as GatewayServerEnv, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LocalConfigSyncResult, LogEntry, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, OnFunnelError, ProcessListStub, ProcessSnapshot, ProfileConfig, ProfileSpec, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SqliteFunnelEventLog, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };
4609
+ export { AliveStub, AttachOptions, BroadcastEvent, BroadcastSubscriber, CONNECTOR_CONNECTION_STATUSES, ChannelConfig, ChannelConnectorView, ChannelDeliveryMode, ChannelServerOptions, ChannelSpec, ConnectorConfig, ConnectorConnectionEvent, ConnectorConnectionQuery, ConnectorConnectionRecord, ConnectorConnectionStatus, ConnectorDiagnosticLog, ConnectorDiagnosticSqlReader, ConnectorProcessedEvent, ConnectorProcessedQuery, ConnectorProcessedRecord, ConnectorQuery, ConnectorRawEvent, ConnectorRawQuery, ConnectorRawRecord, ConnectorSpec, ConnectorSyncOutcome, ConnectorType, DEFAULT_GATEWAY_TOKEN_PATH, DetachOptions, DiscordConnectorConfig, Env, FUNNEL_DIR, FUNNEL_MCP_COMMAND, FUNNEL_MCP_NAME, FileStat, Funnel, FunnelBroadcaster, FunnelChannelPublisher, FunnelChannels, FunnelClaude, FunnelClock, FunnelConnectorFactory, FunnelConnectorListener, FunnelDotenvReader, FunnelEvent, FunnelEventLog, FunnelEventRecord, FunnelFileSystem, FunnelGateway, FunnelGatewayServer, FunnelGatewayToken, FunnelIdGenerator, FunnelListenerSupervisor, FunnelListenersClient, FunnelLocalConfig, FunnelLocalConfigSync, FunnelLogger, FunnelMcp, FunnelProcessRunner, FunnelProfiles, FunnelSettingsReader, FunnelSettingsStore, FunnelSlackEventProcessor, FunnelTokenPrompter, type GatewayEmitInput, type GatewayRouteDeps, type Env$1 as GatewayServerEnv, GhConnectorConfig, LOCAL_CONFIG_FILENAME, LOCAL_ENV_FILENAME, LaunchOptions, ListListenersResult, ListenerEntry, ListenerOpResult, LocalConfig, LocalConfigSyncResult, LogEntry, MemoryConnectorDiagnosticLog, MemoryFunnelClock, MemoryFunnelEventLog, MemoryFunnelFileSystem, MemoryFunnelIdGenerator, MemoryFunnelLogger, MemoryFunnelProcessRunner, MemoryFunnelTokenPrompter, MemoryProcessCall, MemoryProcessHandler, MemoryProcessResponse, MemoryProcessSyncHandler, MockFunnelSettingsReader, NodeFunnelClock, NodeFunnelFileSystem, NodeFunnelIdGenerator, NodeFunnelLogger, NodeFunnelProcessRunner, NodeFunnelTokenPrompter, NoopFunnelLogger, NotifyFn, OnFunnelError, ProcessListStub, ProcessSnapshot, ProfileConfig, ProfileSpec, PublishRequest, PublishResponse, PublishResult, ReplayableEvent, RunOptions, RunResult, SETTINGS_PATH, SETTINGS_VERSION, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleListenerOptions, Settings, SlackConnectorConfig, SlackListenerOptions, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SlackSkipReason, SqliteConnectorDiagnosticLog, SqliteFunnelEventLog, StoredConnectionEvent, StoredProcessedEvent, StoredRawEvent, channelConfigSchema, channelDeliveryModeSchema, channelSpecSchema, app as cliApp, connectorConfigSchema, connectorConnectionEventSchema, connectorProcessedEventSchema, connectorRawEventSchema, connectorSpecSchema, createCliApp, createSettings, discordConnectorSchema, factory, funnelEventSchema, funnelJsonSchema, ghConnectorSchema, launchTui, localConfigSchema, profileConfigSchema, profileSpecSchema, publishRequestSchema, publishResponseSchema, queryToCliArgs, resolveFunnelDir, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema, settingsSchema, slackConnectorSchema, startChannelServer, toRequest };