@interactive-inc/claude-funnel 0.26.1 → 0.28.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-OPpPi9V9.d.ts +208 -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 +221 -39
  16. package/dist/index.js +781 -104
  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-3M6WkH1Y.d.ts} +10 -3
  20. package/dist/{slack-connector-schema-BWL7dWlY.js → slack-connector-schema-CHbRJHGp.js} +140 -19
  21. package/dist/slack-listener-CLTiOEJw.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-OPpPi9V9.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-3M6WkH1Y.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-CLTiOEJw.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
@@ -1066,6 +1094,9 @@ declare abstract class FunnelEventLog {
1066
1094
  abstract record(record: FunnelEventRecord): void;
1067
1095
  abstract loadSince(since: number): ReplayableEvent[];
1068
1096
  abstract findMaxOffset(): number;
1097
+ /** Drop every stored event and reclaim the file. The broadcaster's in-memory
1098
+ * offset counter is unaffected, so offsets keep increasing after a clear. */
1099
+ abstract clear(): void;
1069
1100
  abstract close(): void;
1070
1101
  }
1071
1102
  //#endregion
@@ -1324,7 +1355,7 @@ declare class FunnelListenersClient {
1324
1355
  }
1325
1356
  //#endregion
1326
1357
  //#region lib/funnel.d.ts
1327
- type Props$6 = {
1358
+ type Props$8 = {
1328
1359
  /** 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
1360
  fs?: FunnelFileSystem; /** Process runner used by gateway / claude / gh listener. Replace with MemoryFunnelProcessRunner for tests. */
1330
1361
  process?: FunnelProcessRunner; /** Logger flowed into every facet. Replace with MemoryFunnelLogger or NoopFunnelLogger to silence/inspect. */
@@ -1345,6 +1376,13 @@ type Props$6 = {
1345
1376
  * each successful fire, useful for dropping one-shot entries.
1346
1377
  */
1347
1378
  scheduleListenerOptions?: ScheduleListenerOptions;
1379
+ /**
1380
+ * Diagnostic log of inbound connector traffic (raw events before filtering
1381
+ * and the processor's verdict after). Threaded into listeners that record
1382
+ * it. Only the gateway daemon injects a `SqliteConnectorDiagnosticLog`; everywhere
1383
+ * else this stays absent and recording is a no-op.
1384
+ */
1385
+ diagnosticLog?: ConnectorDiagnosticLog;
1348
1386
  /**
1349
1387
  * Called when Funnel catches an exception that would otherwise be silently
1350
1388
  * swallowed (subscriber throw, listener start/stop failure, etc.). Pass
@@ -1374,13 +1412,13 @@ type Props$6 = {
1374
1412
  declare class Funnel {
1375
1413
  private readonly props;
1376
1414
  private readonly memos;
1377
- constructor(props?: Props$6);
1415
+ constructor(props?: Props$8);
1378
1416
  /**
1379
1417
  * Sandboxed Funnel wired with in-memory implementations for every IO boundary.
1380
1418
  * Touches no real disk, processes, wall-clock time, or UUIDs — safe for tests
1381
1419
  * and ad-hoc experiments. Override individual fields by passing them in `props`.
1382
1420
  */
1383
- static inMemory(props?: Props$6): Funnel;
1421
+ static inMemory(props?: Props$8): Funnel;
1384
1422
  /** Resolved on-disk paths the facade will read/write when methods are called. Pure compute, not memoized. */
1385
1423
  get paths(): {
1386
1424
  dir: string;
@@ -1478,6 +1516,14 @@ declare const startChannelServer: (options?: ChannelServerOptions) => Promise<vo
1478
1516
  declare const funnelJsonSchema: () => Record<string, unknown>;
1479
1517
  //#endregion
1480
1518
  //#region lib/engine/settings/settings-store.d.ts
1519
+ /**
1520
+ * Resolves the funnel home dir. Defaults to `~/.funnel`, overridable via
1521
+ * `FUNNEL_DIR` so a funnel.json-scoped launch can point everything (settings,
1522
+ * gateway pid/token, claude pids) at a repo-local `<repo>/.funnel` and never
1523
+ * touch the global home. Read at call time, not module load, so a daemon
1524
+ * spawned with the env set resolves the override.
1525
+ */
1526
+ declare function resolveFunnelDir(): string;
1481
1527
  declare const FUNNEL_DIR: string;
1482
1528
  declare const SETTINGS_PATH: string;
1483
1529
  type Deps = {
@@ -1529,7 +1575,7 @@ declare class NodeFunnelFileSystem extends FunnelFileSystem {
1529
1575
  }
1530
1576
  //#endregion
1531
1577
  //#region lib/engine/fs/memory-file-system.d.ts
1532
- type Props$5 = {
1578
+ type Props$7 = {
1533
1579
  dirs?: string[];
1534
1580
  files?: Record<string, string>;
1535
1581
  mtimes?: Record<string, number>;
@@ -1542,7 +1588,7 @@ declare class MemoryFunnelFileSystem extends FunnelFileSystem {
1542
1588
  private readonly mtimes;
1543
1589
  private readonly modes;
1544
1590
  private readonly now;
1545
- constructor(props?: Props$5);
1591
+ constructor(props?: Props$7);
1546
1592
  existsSync(path: string): boolean;
1547
1593
  readFileSync(path: string): string;
1548
1594
  writeFileSync(path: string, data: string): void;
@@ -1628,14 +1674,14 @@ declare class MemoryFunnelProcessRunner extends FunnelProcessRunner {
1628
1674
  }
1629
1675
  //#endregion
1630
1676
  //#region lib/engine/logger/node-logger.d.ts
1631
- type Props$4 = {
1677
+ type Props$6 = {
1632
1678
  file?: string;
1633
1679
  now?: () => Date;
1634
1680
  };
1635
1681
  declare class NodeFunnelLogger extends FunnelLogger {
1636
1682
  readonly file: string;
1637
1683
  private readonly now;
1638
- constructor(props?: Props$4);
1684
+ constructor(props?: Props$6);
1639
1685
  info(message: string, meta?: Record<string, unknown>): void;
1640
1686
  warn(message: string, meta?: Record<string, unknown>): void;
1641
1687
  error(message: string, meta?: Record<string, unknown>): void;
@@ -1671,12 +1717,12 @@ declare class NodeFunnelClock extends FunnelClock {
1671
1717
  }
1672
1718
  //#endregion
1673
1719
  //#region lib/engine/time/memory-clock.d.ts
1674
- type Props$3 = {
1720
+ type Props$5 = {
1675
1721
  start?: Date;
1676
1722
  };
1677
1723
  declare class MemoryFunnelClock extends FunnelClock {
1678
1724
  private current;
1679
- constructor(props?: Props$3);
1725
+ constructor(props?: Props$5);
1680
1726
  now(): Date;
1681
1727
  set(date: Date): void;
1682
1728
  advance(ms: number): void;
@@ -1688,13 +1734,13 @@ declare class NodeFunnelIdGenerator extends FunnelIdGenerator {
1688
1734
  }
1689
1735
  //#endregion
1690
1736
  //#region lib/engine/id/memory-id-generator.d.ts
1691
- type Props$2 = {
1737
+ type Props$4 = {
1692
1738
  prefix?: string;
1693
1739
  };
1694
1740
  declare class MemoryFunnelIdGenerator extends FunnelIdGenerator {
1695
1741
  private counter;
1696
1742
  private readonly prefix;
1697
- constructor(props?: Props$2);
1743
+ constructor(props?: Props$4);
1698
1744
  generate(): string;
1699
1745
  }
1700
1746
  //#endregion
@@ -1711,7 +1757,7 @@ declare class NodeFunnelTokenPrompter extends FunnelTokenPrompter {
1711
1757
  }
1712
1758
  //#endregion
1713
1759
  //#region lib/engine/token-prompter/memory-token-prompter.d.ts
1714
- type Props$1 = {
1760
+ type Props$3 = {
1715
1761
  answers?: Record<string, string>;
1716
1762
  };
1717
1763
  /**
@@ -1721,16 +1767,18 @@ type Props$1 = {
1721
1767
  declare class MemoryFunnelTokenPrompter extends FunnelTokenPrompter {
1722
1768
  private readonly answers;
1723
1769
  readonly asked: string[];
1724
- constructor(props?: Props$1);
1770
+ constructor(props?: Props$3);
1725
1771
  promptSecret(label: string): Promise<string>;
1726
1772
  }
1727
1773
  //#endregion
1728
1774
  //#region lib/gateway/sqlite-funnel-event-log.d.ts
1729
- type Props = {
1775
+ type Props$2 = {
1730
1776
  /** SQLite database file path. Created on first write. ":memory:" for tests. */path: string; /** Override for tests. Defaults to `Date.now`. */
1731
1777
  now?: () => number; /** Optional row cap. Pruned on every insert. */
1732
1778
  maxRows?: number; /** Optional age cap in ms. Pruned on every insert. */
1733
- maxAgeMs?: number;
1779
+ maxAgeMs?: number; /** Optional on-disk byte cap. Checked periodically; on overflow the oldest rows are dropped toward targetBytes and the file is VACUUMed. */
1780
+ maxBytes?: number; /** Shrink target when maxBytes is exceeded. Defaults to maxBytes/4. */
1781
+ targetBytes?: number;
1734
1782
  };
1735
1783
  /**
1736
1784
  * SQLite-backed `FunnelEventLog`. One indexed table holds every broadcaster
@@ -1752,7 +1800,7 @@ type Props = {
1752
1800
  declare class SqliteFunnelEventLog extends FunnelEventLog {
1753
1801
  private readonly sink;
1754
1802
  private readonly now;
1755
- constructor(props: Props);
1803
+ constructor(props: Props$2);
1756
1804
  /**
1757
1805
  * Persist a broadcaster-driven event with its assigned offset. Caller
1758
1806
  * (the gateway-server) supplies the offset from `broadcaster.broadcast()`
@@ -1777,6 +1825,7 @@ declare class SqliteFunnelEventLog extends FunnelEventLog {
1777
1825
  limit?: number;
1778
1826
  }): ReplayableEvent[];
1779
1827
  findMaxOffset(): number;
1828
+ clear(): void;
1780
1829
  close(): void;
1781
1830
  }
1782
1831
  //#endregion
@@ -1797,6 +1846,113 @@ declare class MemoryFunnelEventLog extends FunnelEventLog {
1797
1846
  close(): void;
1798
1847
  }
1799
1848
  //#endregion
1849
+ //#region lib/gateway/sqlite-connector-diagnostic-log.d.ts
1850
+ type Props$1 = {
1851
+ /** SQLite file for the raw (pre-filter) table. ":memory:" for tests. */rawPath: string; /** SQLite file for the processed (verdict) table. ":memory:" for tests. */
1852
+ processedPath: string; /** SQLite file for the connection (lifecycle) table. ":memory:" for tests. */
1853
+ connectionPath: string;
1854
+ now?: () => number; /** Row cap for the processed and connection tables. Pruned on every insert. */
1855
+ maxRows?: number;
1856
+ /**
1857
+ * Row cap for the raw table specifically. Raw rows can each hold up to
1858
+ * `RAW_PAYLOAD_CAP` bytes, so they want a tighter cap than the small
1859
+ * processed/connection verdict rows. Defaults to `maxRows` when unset.
1860
+ */
1861
+ rawMaxRows?: number; /** Age cap in ms for all tables — bounds how long untouched payloads (with PII) live. Pruned on every insert. */
1862
+ maxAgeMs?: number; /** When set, `insert()` errors (disk full, WAL lock) are logged instead of silently dropped. */
1863
+ logger?: FunnelLogger;
1864
+ };
1865
+ /**
1866
+ * Default `ConnectorDiagnosticLog`: three independent `LeucoLoggerSqliteSink`s, one
1867
+ * per table (raw / processed / connection), in separate files. Each sink
1868
+ * indexes the columns its queries filter on — `event_id` / `connector_id` /
1869
+ * `channel_id` for raw, plus `outcome` for processed and `status` for
1870
+ * connection — so those lookups are indexed scans (`type` is a fixed column
1871
+ * the sink extracts separately, not an index, so filtering by it is a scan).
1872
+ *
1873
+ * The raw table offloads any payload over `RAW_PAYLOAD_CAP`: rather than
1874
+ * truncating mid-string (which yields unparseable JSON), it replaces the
1875
+ * body with a small JSON object that keeps the diagnostic essentials and
1876
+ * records the dropped size under `_funnel_oversized`. Every stored payload
1877
+ * therefore stays valid JSON.
1878
+ */
1879
+ declare class SqliteConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1880
+ private readonly raw;
1881
+ private readonly processed;
1882
+ private readonly connection;
1883
+ private readonly now;
1884
+ private readonly logger;
1885
+ constructor(props: Props$1);
1886
+ recordRaw(record: ConnectorRawRecord): void;
1887
+ recordProcessed(record: ConnectorProcessedRecord): void;
1888
+ recordConnection(record: ConnectorConnectionRecord): void;
1889
+ private report;
1890
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1891
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1892
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1893
+ clear(): void;
1894
+ close(): void;
1895
+ }
1896
+ //#endregion
1897
+ //#region lib/gateway/memory-connector-diagnostic-log.d.ts
1898
+ /**
1899
+ * In-process `ConnectorDiagnosticLog` backed by one array per table. Used by tests
1900
+ * and embedders that do not need durability. Like the SQLite log it keeps
1901
+ * `seq` per-table (each array's 1-based position) and returns the most recent
1902
+ * `limit` rows oldest-first; unlike it, it never prunes and never offloads
1903
+ * oversized payloads — it keeps whatever the caller hands it, which is fine
1904
+ * for the bounded volumes a test produces. Payload-validity is therefore a
1905
+ * SQLite-only guarantee; do not write a test that leans on this double
1906
+ * rejecting a malformed payload.
1907
+ */
1908
+ declare class MemoryConnectorDiagnosticLog extends ConnectorDiagnosticLog {
1909
+ private readonly now;
1910
+ private readonly raws;
1911
+ private readonly processeds;
1912
+ private readonly connections;
1913
+ constructor(now?: () => number);
1914
+ recordRaw(record: ConnectorRawRecord): void;
1915
+ recordProcessed(record: ConnectorProcessedRecord): void;
1916
+ recordConnection(record: ConnectorConnectionRecord): void;
1917
+ queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
1918
+ queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
1919
+ queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
1920
+ close(): void;
1921
+ }
1922
+ //#endregion
1923
+ //#region lib/gateway/connector-diagnostic-sql-reader.d.ts
1924
+ type Props = {
1925
+ /** SQLite file holding the raw (pre-filter) table. */rawPath: string; /** SQLite file holding the processed (verdict) table. */
1926
+ processedPath: string; /** SQLite file holding the connection (lifecycle) table. */
1927
+ connectionPath: string;
1928
+ };
1929
+ type Row = Record<string, unknown>;
1930
+ /**
1931
+ * Read-only SQL surface over the three diagnostic tables, for Claude to query
1932
+ * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
1933
+ * three views — `raw`, `processed`, `connection` — that hide the storage
1934
+ * details (the physical table is `leuco_log` and each row's columns live
1935
+ * inside a JSON `event` blob): the views surface the columns as plain fields,
1936
+ * with `payload` already pulled out of the nested JSON.
1937
+ *
1938
+ * The tables are separate files. `raw` and `processed` share an `event_id`,
1939
+ * so a `JOIN` answers "the event arrived, but what verdict did it get?";
1940
+ * `connection` answers the other half — "did the listener ever connect at
1941
+ * all?". Writes are impossible: the connection is read-only and `query`
1942
+ * rejects anything but a single `SELECT`.
1943
+ */
1944
+ declare class ConnectorDiagnosticSqlReader {
1945
+ private readonly db;
1946
+ constructor(props: Props);
1947
+ /**
1948
+ * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
1949
+ * than throwing) for a non-SELECT statement or a SQL error, so the caller
1950
+ * can surface the message without a stack trace.
1951
+ */
1952
+ query(sql: string): Row[] | Error;
1953
+ close(): void;
1954
+ }
1955
+ //#endregion
1800
1956
  //#region lib/cli/factory.d.ts
1801
1957
  type Env = {
1802
1958
  Variables: {
@@ -3051,6 +3207,19 @@ declare const createCliApp: (funnel: Funnel) => _$hono_hono_base0.HonoBase<Env,
3051
3207
  status: _$hono_utils_http_status0.ContentfulStatusCode;
3052
3208
  };
3053
3209
  };
3210
+ } & {
3211
+ "/gateway/sql": {
3212
+ $get: {
3213
+ input: {
3214
+ query: {
3215
+ query?: string | undefined;
3216
+ };
3217
+ };
3218
+ output: string;
3219
+ outputFormat: "text";
3220
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
3221
+ };
3222
+ };
3054
3223
  } & {
3055
3224
  "/gateway/listeners": {
3056
3225
  $get: {
@@ -4352,6 +4521,19 @@ declare const app: _$hono_hono_base0.HonoBase<Env, {
4352
4521
  status: _$hono_utils_http_status0.ContentfulStatusCode;
4353
4522
  };
4354
4523
  };
4524
+ } & {
4525
+ "/gateway/sql": {
4526
+ $get: {
4527
+ input: {
4528
+ query: {
4529
+ query?: string | undefined;
4530
+ };
4531
+ };
4532
+ output: string;
4533
+ outputFormat: "text";
4534
+ status: _$hono_utils_http_status0.ContentfulStatusCode;
4535
+ };
4536
+ };
4355
4537
  } & {
4356
4538
  "/gateway/listeners": {
4357
4539
  $get: {
@@ -4431,4 +4613,4 @@ ${string}`;
4431
4613
  //#region lib/tui/tui.d.ts
4432
4614
  declare function launchTui(funnel: Funnel): Promise<void>;
4433
4615
  //#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 };
4616
+ 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 };