@interactive-inc/claude-funnel 0.53.0 → 0.56.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 (71) hide show
  1. package/README.md +3 -3
  2. package/dist/bin.js +1229 -486
  3. package/dist/claude.d.ts +22 -5
  4. package/dist/claude.js +455 -168
  5. package/dist/{connector-adapter-CePYBTgW.d.ts → connector-adapter-1PxjN-Uk.d.ts} +1 -1
  6. package/dist/{connector-adapter-D5Utumgz.js → connector-adapter-qwXLjQId.js} +1 -1
  7. package/dist/{connector-listener-DU54DN-f.js → connector-listener-CpHBecCj.js} +1 -1
  8. package/dist/connectors/discord.d.ts +6 -6
  9. package/dist/connectors/discord.js +2 -2
  10. package/dist/connectors/gh.d.ts +6 -6
  11. package/dist/connectors/gh.js +2 -2
  12. package/dist/connectors/schedule.d.ts +12 -2
  13. package/dist/connectors/schedule.js +2 -2
  14. package/dist/connectors/slack.d.ts +3 -3
  15. package/dist/connectors/slack.js +2 -2
  16. package/dist/{connector-diagnostic-log-yTOojKUR.d.ts → diagnostic-log-Bxe7Bbvw.d.ts} +2 -2
  17. package/dist/diagnostic-sql-reader-CzYgZpq2.js +83 -0
  18. package/dist/diagnostics.d.ts +2 -0
  19. package/dist/diagnostics.js +2 -0
  20. package/dist/{discord-connector-schema-CBDyGdOI.js → discord-connector-schema-B_N6IXLz.js} +1 -1
  21. package/dist/{discord-connector-schema-R0Uu-3ns.d.ts → discord-connector-schema-CPgcZkXh.d.ts} +1 -1
  22. package/dist/{discord-listener-_jSE3HsQ.js → discord-listener-C0MoKdQO.js} +6 -6
  23. package/dist/docs.d.ts +2 -0
  24. package/dist/docs.js +2 -0
  25. package/dist/doctor.d.ts +2 -0
  26. package/dist/doctor.js +2 -0
  27. package/dist/{file-process-guard-DMeLB6Zd.d.ts → file-process-guard-DI1742H5.d.ts} +5 -4
  28. package/dist/funnel-diagnostics-BpKYrMSu.js +300 -0
  29. package/dist/funnel-diagnostics-qWy5tPSq.d.ts +176 -0
  30. package/dist/funnel-docs-dXPokzr5.d.ts +18 -0
  31. package/dist/funnel-docs-ng5K8w4j.js +653 -0
  32. package/dist/funnel-doctor-BF3Rdgk0.d.ts +34 -0
  33. package/dist/funnel-doctor-CApCezTq.js +82 -0
  34. package/dist/funnel-recovery-BUBsu7WX.d.ts +101 -0
  35. package/dist/funnel-recovery-D9CxD5Zs.js +134 -0
  36. package/dist/gateway/daemon.js +838 -252
  37. package/dist/{gateway-base-url-ssk_He5G.js → gateway-base-url-6foMXfFf.js} +5 -5
  38. package/dist/gateway.d.ts +2 -2
  39. package/dist/gateway.js +2 -2
  40. package/dist/{gh-connector-schema-eoTtHbY6.d.ts → gh-connector-schema-CU1ojfIF.d.ts} +1 -1
  41. package/dist/{gh-connector-schema-o3Q1-ojL.js → gh-connector-schema-DUcZgN2Q.js} +1 -1
  42. package/dist/{gh-listener-DH-fClQm.js → gh-listener-Dsx6AmhH.js} +5 -5
  43. package/dist/{index-DF5VmCPJ.d.ts → index-CrngHrne.d.ts} +104 -607
  44. package/dist/index.d.ts +16 -11
  45. package/dist/index.js +509 -973
  46. package/dist/{local-config-json-schema-D8i-BogY.js → local-config-json-schema-DE1zkMcb.js} +12 -8
  47. package/dist/{local-config-sync-Cq39mT6p.d.ts → local-config-sync-B8b04LrZ.d.ts} +21 -16
  48. package/dist/local-config.d.ts +2 -2
  49. package/dist/local-config.js +2 -2
  50. package/dist/{memory-connector-diagnostic-log-COUWCsT_.js → memory-diagnostic-log-BbFVqDzz.js} +30 -95
  51. package/dist/{memory-token-prompter-CKV7VBM5.d.ts → memory-token-prompter-Lo3YRDzq.d.ts} +4 -4
  52. package/dist/{memory-token-prompter-Q7Snwsv2.js → memory-token-prompter-vBXxY20-.js} +2 -2
  53. package/dist/{profiles-f0mNmEyP.d.ts → profiles-EHTeCOqB.d.ts} +3 -2
  54. package/dist/profiles.d.ts +1 -1
  55. package/dist/profiles.js +1 -1
  56. package/dist/recovery.d.ts +2 -0
  57. package/dist/recovery.js +2 -0
  58. package/dist/{resolve-connector-token-BHmZLRrV.js → resolve-connector-token-CczqG_Ig.js} +1 -1
  59. package/dist/{schedule-connector-schema-iCI61gzU.js → schedule-connector-schema-B_xO5z5B.js} +1 -1
  60. package/dist/{schedule-listener-CUyUFFR1.d.ts → schedule-listener-DKh0hnkK.d.ts} +5 -5
  61. package/dist/{schedule-listener-ePAjians.js → schedule-listener-DP9Jhc6U.js} +14 -4
  62. package/dist/settings-reader-CBrgz01o.d.ts +18 -0
  63. package/dist/{settings-reader-BSU6JyvM.d.ts → settings-schema-zhnMIa8I.d.ts} +1 -16
  64. package/dist/{slack-connector-schema-BCNWluHM.js → slack-connector-schema-C1zEf4TG.js} +1 -1
  65. package/dist/{slack-listener-Bv5xI9gC.d.ts → slack-listener-COQA8wAZ.d.ts} +4 -4
  66. package/dist/{slack-listener-ClQuHhEF.js → slack-listener-DUKPcpJH.js} +7 -7
  67. package/dist/{mcp-QeNCBhOD.js → yaml-render-OhUN-qkS.js} +52 -34
  68. package/package.json +21 -1
  69. /package/dist/{file-system-BeOKXjlV.d.ts → file-system-Wub9Nto4.d.ts} +0 -0
  70. /package/dist/{process-runner-DfniuWVU.d.ts → process-runner-D5I_jhYQ.d.ts} +0 -0
  71. /package/dist/{profiles-wMRnjSid.js → profiles-MnXvYfZF.js} +0 -0
@@ -1,4 +1,4 @@
1
- //#region lib/connectors/connector-adapter.d.ts
1
+ //#region lib/engine/connectors/connector-adapter.d.ts
2
2
  /**
3
3
  * A JSON-serializable value. Connector call bodies are sent to external APIs as
4
4
  * JSON, so the body must be representable as JSON — `JsonValue` says exactly
@@ -1,4 +1,4 @@
1
- //#region lib/connectors/connector-adapter.ts
1
+ //#region lib/engine/connectors/connector-adapter.ts
2
2
  var FunnelConnectorAdapter = class {};
3
3
  //#endregion
4
4
  export { FunnelConnectorAdapter as t };
@@ -1,4 +1,4 @@
1
- //#region lib/connectors/connector-listener.ts
1
+ //#region lib/engine/connectors/connector-listener.ts
2
2
  /**
3
3
  * Long-lived event source for one connector.
4
4
  *
@@ -1,6 +1,6 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../connector-diagnostic-log-yTOojKUR.js";
2
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-CePYBTgW.js";
3
- import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "../discord-connector-schema-R0Uu-3ns.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Bxe7Bbvw.js";
2
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
3
+ import { n as discordConnectorSchema, t as DiscordConnectorConfig } from "../discord-connector-schema-CPgcZkXh.js";
4
4
 
5
5
  //#region lib/engine/http/http-client.d.ts
6
6
  type HttpRequest = {
@@ -19,7 +19,7 @@ declare abstract class FunnelHttpClient {
19
19
  abstract fetch(request: HttpRequest): Promise<HttpResponse>;
20
20
  }
21
21
  //#endregion
22
- //#region lib/connectors/discord-adapter.d.ts
22
+ //#region lib/engine/connectors/discord-adapter.d.ts
23
23
  type Deps$1 = {
24
24
  config: DiscordConnectorConfig; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
25
25
  env?: NodeJS.ProcessEnv;
@@ -32,7 +32,7 @@ declare class FunnelDiscordAdapter extends FunnelConnectorAdapter {
32
32
  call(input: CallInput): Promise<unknown>;
33
33
  }
34
34
  //#endregion
35
- //#region lib/connectors/discord-event-processor.d.ts
35
+ //#region lib/engine/connectors/discord-event-processor.d.ts
36
36
  type DiscordInboundMessage = {
37
37
  authorId: string;
38
38
  authorIsBot: boolean;
@@ -59,7 +59,7 @@ declare class FunnelDiscordEventProcessor {
59
59
  process(message: DiscordInboundMessage): DiscordProcessed;
60
60
  }
61
61
  //#endregion
62
- //#region lib/connectors/discord-listener.d.ts
62
+ //#region lib/engine/connectors/discord-listener.d.ts
63
63
  type Deps = {
64
64
  config: DiscordConnectorConfig; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
65
65
  channelId?: string; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
@@ -1,3 +1,3 @@
1
- import { n as FunnelDiscordEventProcessor, r as FunnelDiscordAdapter, t as FunnelDiscordListener } from "../discord-listener-_jSE3HsQ.js";
2
- import { t as discordConnectorSchema } from "../discord-connector-schema-CBDyGdOI.js";
1
+ import { n as FunnelDiscordEventProcessor, r as FunnelDiscordAdapter, t as FunnelDiscordListener } from "../discord-listener-C0MoKdQO.js";
2
+ import { t as discordConnectorSchema } from "../discord-connector-schema-B_N6IXLz.js";
3
3
  export { FunnelDiscordAdapter, FunnelDiscordEventProcessor, FunnelDiscordListener, discordConnectorSchema };
@@ -1,9 +1,9 @@
1
- import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../connector-diagnostic-log-yTOojKUR.js";
2
- import { r as FunnelProcessRunner } from "../process-runner-DfniuWVU.js";
3
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-CePYBTgW.js";
4
- import { n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-eoTtHbY6.js";
1
+ import { S as FunnelLogger, b as FunnelConnectorListener, o as ConnectorDiagnosticLog, x as NotifyFn } from "../diagnostic-log-Bxe7Bbvw.js";
2
+ import { r as FunnelProcessRunner } from "../process-runner-D5I_jhYQ.js";
3
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
4
+ import { n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-CU1ojfIF.js";
5
5
 
6
- //#region lib/connectors/gh-adapter.d.ts
6
+ //#region lib/engine/connectors/gh-adapter.d.ts
7
7
  type Deps$1 = {
8
8
  process?: FunnelProcessRunner;
9
9
  };
@@ -13,7 +13,7 @@ declare class FunnelGhAdapter extends FunnelConnectorAdapter {
13
13
  call(input: CallInput): Promise<unknown>;
14
14
  }
15
15
  //#endregion
16
- //#region lib/connectors/gh-listener.d.ts
16
+ //#region lib/engine/connectors/gh-listener.d.ts
17
17
  type Deps = {
18
18
  config: GhConnectorConfig; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
19
19
  channelId?: string;
@@ -1,3 +1,3 @@
1
- import { t as ghConnectorSchema } from "../gh-connector-schema-o3Q1-ojL.js";
2
- import { n as FunnelGhAdapter, t as FunnelGhListener } from "../gh-listener-DH-fClQm.js";
1
+ import { t as ghConnectorSchema } from "../gh-connector-schema-DUcZgN2Q.js";
2
+ import { n as FunnelGhAdapter, t as FunnelGhListener } from "../gh-listener-Dsx6AmhH.js";
3
3
  export { FunnelGhAdapter, FunnelGhListener, ghConnectorSchema };
@@ -1,6 +1,16 @@
1
- import { a as ScheduleEntry, c as scheduleEntrySchema, i as ScheduleConnectorConfig, l as ScheduleStateStore, n as ScheduleOnFired, o as scheduleCatchupPolicySchema, r as ScheduleCatchupPolicy, s as scheduleConnectorSchema, t as FunnelScheduleListener } from "../schedule-listener-CUyUFFR1.js";
1
+ import { a as ScheduleEntry, c as scheduleEntrySchema, i as ScheduleConnectorConfig, l as ScheduleStateStore, n as ScheduleOnFired, o as scheduleCatchupPolicySchema, r as ScheduleCatchupPolicy, s as scheduleConnectorSchema, t as FunnelScheduleListener } from "../schedule-listener-DKh0hnkK.js";
2
2
 
3
- //#region lib/connectors/match-cron.d.ts
3
+ //#region lib/engine/connectors/match-cron.d.ts
4
+ /**
5
+ * Returns true when `date` (local time) satisfies a 5-field cron expression.
6
+ *
7
+ * Two deliberate deviations from Vixie cron, called out so schedules are not
8
+ * written expecting the other behavior:
9
+ * - Day-of-month and day-of-week are ANDed, not ORed. Vixie cron fires when
10
+ * EITHER matches once both are restricted; here every field must match.
11
+ * - Day-of-week is 0-6 (Sunday=0). `7` for Sunday is NOT accepted (it throws
12
+ * as out-of-range); use `0`.
13
+ */
4
14
  declare const matchCron: (expr: string, date: Date) => boolean;
5
15
  //#endregion
6
16
  export { FunnelScheduleListener, ScheduleCatchupPolicy, ScheduleConnectorConfig, ScheduleEntry, ScheduleOnFired, ScheduleStateStore, matchCron, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema };
@@ -1,3 +1,3 @@
1
- import { n as ScheduleStateStore, r as matchCron, t as FunnelScheduleListener } from "../schedule-listener-ePAjians.js";
2
- import { n as scheduleConnectorSchema, r as scheduleEntrySchema, t as scheduleCatchupPolicySchema } from "../schedule-connector-schema-iCI61gzU.js";
1
+ import { n as ScheduleStateStore, r as matchCron, t as FunnelScheduleListener } from "../schedule-listener-DP9Jhc6U.js";
2
+ import { n as scheduleConnectorSchema, r as scheduleEntrySchema, t as scheduleCatchupPolicySchema } from "../schedule-connector-schema-B_xO5z5B.js";
3
3
  export { FunnelScheduleListener, ScheduleStateStore, matchCron, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema };
@@ -1,7 +1,7 @@
1
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-CePYBTgW.js";
2
- import { a as slackConnectorSchema, c as SlackProcessedEmit, d as SlackSkipReason, i as SlackConnectorConfig, l as SlackProcessedSkip, n as SlackOnAppCreated, o as FunnelSlackEventProcessor, r as SlackPreprocessEvent, s as SlackProcessed, t as FunnelSlackListener, u as SlackRawEvent } from "../slack-listener-Bv5xI9gC.js";
1
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-1PxjN-Uk.js";
2
+ import { a as slackConnectorSchema, c as SlackProcessedEmit, d as SlackSkipReason, i as SlackConnectorConfig, l as SlackProcessedSkip, n as SlackOnAppCreated, o as FunnelSlackEventProcessor, r as SlackPreprocessEvent, s as SlackProcessed, t as FunnelSlackListener, u as SlackRawEvent } from "../slack-listener-COQA8wAZ.js";
3
3
 
4
- //#region lib/connectors/slack-adapter.d.ts
4
+ //#region lib/engine/connectors/slack-adapter.d.ts
5
5
  type SlackWebClientLike = {
6
6
  apiCall: (method: string, options?: Record<string, unknown>) => Promise<unknown>;
7
7
  };
@@ -1,3 +1,3 @@
1
- import { n as FunnelSlackEventProcessor, r as FunnelSlackAdapter, t as FunnelSlackListener } from "../slack-listener-ClQuHhEF.js";
2
- import { t as slackConnectorSchema } from "../slack-connector-schema-BCNWluHM.js";
1
+ import { n as FunnelSlackEventProcessor, r as FunnelSlackAdapter, t as FunnelSlackListener } from "../slack-listener-DUKPcpJH.js";
2
+ import { t as slackConnectorSchema } from "../slack-connector-schema-C1zEf4TG.js";
3
3
  export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, slackConnectorSchema };
@@ -13,7 +13,7 @@ declare abstract class FunnelLogger {
13
13
  abstract readonly file: string | null;
14
14
  }
15
15
  //#endregion
16
- //#region lib/connectors/connector-listener.d.ts
16
+ //#region lib/engine/connectors/connector-listener.d.ts
17
17
  type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>;
18
18
  /**
19
19
  * Long-lived event source for one connector.
@@ -32,7 +32,7 @@ declare abstract class FunnelConnectorListener {
32
32
  isAlive(): boolean;
33
33
  }
34
34
  //#endregion
35
- //#region lib/gateway/connector-diagnostic-log.d.ts
35
+ //#region lib/gateway/diagnostic-log/diagnostic-log.d.ts
36
36
  /**
37
37
  * Points in the listener's connection lifecycle. The single source of truth
38
38
  * for the value set: the `status` column schema, the `ConnectorConnectionStatus`
@@ -0,0 +1,83 @@
1
+ import { Database } from "bun:sqlite";
2
+ //#region lib/gateway/diagnostic-log/diagnostic-sql-reader.ts
3
+ /**
4
+ * Read-only SQL surface over the three diagnostic tables, for Claude to query
5
+ * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
6
+ * three views — `raw`, `processed`, `connection` — that hide the storage
7
+ * details (the physical table is `leuco_log` and each row's columns live
8
+ * inside a JSON `event` blob): the views surface the columns as plain fields,
9
+ * with `payload` already pulled out of the nested JSON.
10
+ *
11
+ * The tables are separate files. `raw` and `processed` share an `event_id`,
12
+ * so a `JOIN` answers "the event arrived, but what verdict did it get?";
13
+ * `connection` answers the other half — "did the listener ever connect at
14
+ * all?". Writes are impossible: the connection is read-only and `query`
15
+ * rejects anything but a single `SELECT`.
16
+ */
17
+ var ConnectorDiagnosticSqlReader = class {
18
+ db;
19
+ constructor(props) {
20
+ const db = new Database(props.rawPath, { readonly: true });
21
+ try {
22
+ db.run("PRAGMA busy_timeout = 500");
23
+ db.prepare("ATTACH DATABASE ? AS processeddb").run(props.processedPath);
24
+ db.prepare("ATTACH DATABASE ? AS connectiondb").run(props.connectionPath);
25
+ db.run(rawViewSql);
26
+ db.run(processedViewSql);
27
+ db.run(connectionViewSql);
28
+ } catch (error) {
29
+ db.close();
30
+ throw error;
31
+ }
32
+ this.db = db;
33
+ Object.freeze(this);
34
+ }
35
+ /**
36
+ * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
37
+ * than throwing) for a non-SELECT statement or a SQL error, so the caller
38
+ * can surface the message without a stack trace.
39
+ */
40
+ query(sql, params = []) {
41
+ const trimmed = sql.trim().replace(/;$/, "").trim();
42
+ if (!/^select\b/i.test(trimmed)) return /* @__PURE__ */ new Error("only a single SELECT statement is allowed");
43
+ if (trimmed.includes(";")) return /* @__PURE__ */ new Error("only a single statement is allowed (remove the ';')");
44
+ try {
45
+ return this.db.prepare(trimmed).all(...params);
46
+ } catch (error) {
47
+ return error instanceof Error ? error : new Error(String(error));
48
+ }
49
+ }
50
+ close() {
51
+ this.db.close();
52
+ }
53
+ };
54
+ const rawViewSql = `CREATE TEMP VIEW raw AS SELECT
55
+ seq,
56
+ ts,
57
+ json_extract(event, '$.event_id') AS event_id,
58
+ json_extract(event, '$.type') AS type,
59
+ json_extract(event, '$.connector_id') AS connector_id,
60
+ json_extract(event, '$.channel_id') AS channel_id,
61
+ json_extract(event, '$.payload') AS payload
62
+ FROM main.leuco_log`;
63
+ const processedViewSql = `CREATE TEMP VIEW processed AS SELECT
64
+ seq,
65
+ ts,
66
+ json_extract(event, '$.event_id') AS event_id,
67
+ json_extract(event, '$.type') AS type,
68
+ json_extract(event, '$.connector_id') AS connector_id,
69
+ json_extract(event, '$.channel_id') AS channel_id,
70
+ json_extract(event, '$.outcome') AS outcome,
71
+ json_extract(event, '$.payload') AS payload
72
+ FROM processeddb.leuco_log`;
73
+ const connectionViewSql = `CREATE TEMP VIEW connection AS SELECT
74
+ seq,
75
+ ts,
76
+ json_extract(event, '$.type') AS type,
77
+ json_extract(event, '$.connector_id') AS connector_id,
78
+ json_extract(event, '$.channel_id') AS channel_id,
79
+ json_extract(event, '$.status') AS status,
80
+ json_extract(event, '$.detail') AS detail
81
+ FROM connectiondb.leuco_log`;
82
+ //#endregion
83
+ export { ConnectorDiagnosticSqlReader as t };
@@ -0,0 +1,2 @@
1
+ import { a as DiagnosticsGatewayProbe, c as FunnelDiagnostics, d as DiagnosticEvent, f as previewOf, h as toDiagnosticEvent, i as DiagnosticsChannelSource, l as ReplayResult, m as toDiagnosticConnectionError, n as DiagnoseAllReport, o as DiagnosticsPublisher, p as queryRows, r as DiagnosisStatus, s as DiagnosticsTokenReader, t as ChannelDiagnosis, u as DiagnosticConnectionError } from "./funnel-diagnostics-qWy5tPSq.js";
2
+ export { ChannelDiagnosis, DiagnoseAllReport, DiagnosisStatus, DiagnosticConnectionError, DiagnosticEvent, DiagnosticsChannelSource, DiagnosticsGatewayProbe, DiagnosticsPublisher, DiagnosticsTokenReader, FunnelDiagnostics, ReplayResult, previewOf, queryRows, toDiagnosticConnectionError, toDiagnosticEvent };
@@ -0,0 +1,2 @@
1
+ import { a as toDiagnosticEvent, i as toDiagnosticConnectionError, n as previewOf, r as queryRows, t as FunnelDiagnostics } from "./funnel-diagnostics-BpKYrMSu.js";
2
+ export { FunnelDiagnostics, previewOf, queryRows, toDiagnosticConnectionError, toDiagnosticEvent };
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region lib/connectors/discord-connector-schema.ts
2
+ //#region lib/engine/connectors/discord-connector-schema.ts
3
3
  /**
4
4
  * Like slack, a discord connector holds either a literal `botToken` or a
5
5
  * `botTokenEnv` reference resolved from `process.env` at listener start. The
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region lib/connectors/discord-connector-schema.d.ts
3
+ //#region lib/engine/connectors/discord-connector-schema.d.ts
4
4
  /**
5
5
  * Like slack, a discord connector holds either a literal `botToken` or a
6
6
  * `botTokenEnv` reference resolved from `process.env` at listener start. The
@@ -1,6 +1,6 @@
1
- import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
2
- import { t as resolveConnectorToken } from "./resolve-connector-token-BHmZLRrV.js";
3
- import { t as FunnelConnectorListener } from "./connector-listener-DU54DN-f.js";
1
+ import { t as FunnelConnectorAdapter } from "./connector-adapter-qwXLjQId.js";
2
+ import { t as resolveConnectorToken } from "./resolve-connector-token-CczqG_Ig.js";
3
+ import { t as FunnelConnectorListener } from "./connector-listener-CpHBecCj.js";
4
4
  import { Client, GatewayIntentBits, Partials } from "discord.js";
5
5
  //#region lib/engine/http/http-client.ts
6
6
  var FunnelHttpClient = class {};
@@ -26,7 +26,7 @@ var NodeFunnelHttpClient = class extends FunnelHttpClient {
26
26
  }
27
27
  };
28
28
  //#endregion
29
- //#region lib/connectors/discord-adapter.ts
29
+ //#region lib/engine/connectors/discord-adapter.ts
30
30
  const DISCORD_API_BASE = "https://discord.com/api/v10";
31
31
  const defaultHttp = new NodeFunnelHttpClient();
32
32
  var FunnelDiscordAdapter = class extends FunnelConnectorAdapter {
@@ -63,7 +63,7 @@ var FunnelDiscordAdapter = class extends FunnelConnectorAdapter {
63
63
  }
64
64
  };
65
65
  //#endregion
66
- //#region lib/connectors/discord-event-processor.ts
66
+ //#region lib/engine/connectors/discord-event-processor.ts
67
67
  var FunnelDiscordEventProcessor = class {
68
68
  ownUserId;
69
69
  constructor(props) {
@@ -86,7 +86,7 @@ var FunnelDiscordEventProcessor = class {
86
86
  }
87
87
  };
88
88
  //#endregion
89
- //#region lib/connectors/discord-listener.ts
89
+ //#region lib/engine/connectors/discord-listener.ts
90
90
  var FunnelDiscordListener = class extends FunnelConnectorListener {
91
91
  config;
92
92
  channelId;
package/dist/docs.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { n as FunnelDocs, t as DocsTopicListing } from "./funnel-docs-dXPokzr5.js";
2
+ export { DocsTopicListing, FunnelDocs };
package/dist/docs.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as FunnelDocs } from "./funnel-docs-ng5K8w4j.js";
2
+ export { FunnelDocs };
@@ -0,0 +1,2 @@
1
+ import { n as DoctorReport, r as FunnelDoctor, t as DoctorFixMode } from "./funnel-doctor-BF3Rdgk0.js";
2
+ export { DoctorFixMode, DoctorReport, FunnelDoctor };
package/dist/doctor.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as FunnelDoctor } from "./funnel-doctor-CApCezTq.js";
2
+ export { FunnelDoctor };
@@ -1,7 +1,8 @@
1
- import { n as FunnelIdGenerator, r as ChannelConfig } from "./settings-reader-BSU6JyvM.js";
2
- import { S as FunnelLogger } from "./connector-diagnostic-log-yTOojKUR.js";
3
- import { r as FunnelProcessRunner } from "./process-runner-DfniuWVU.js";
4
- import { n as FunnelFileSystem } from "./file-system-BeOKXjlV.js";
1
+ import { t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
2
+ import { n as FunnelIdGenerator } from "./settings-reader-CBrgz01o.js";
3
+ import { S as FunnelLogger } from "./diagnostic-log-Bxe7Bbvw.js";
4
+ import { r as FunnelProcessRunner } from "./process-runner-D5I_jhYQ.js";
5
+ import { n as FunnelFileSystem } from "./file-system-Wub9Nto4.js";
5
6
 
6
7
  //#region lib/engine/claude/channel-resolver.d.ts
7
8
  type ChannelResolver = {
@@ -0,0 +1,300 @@
1
+ import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-CzYgZpq2.js";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ //#region lib/services/diagnostics/diagnostic-event.ts
5
+ const stringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
6
+ const numberOrNull = (value) => typeof value === "number" ? value : null;
7
+ const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
8
+ const isStringKeyedObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9
+ const parsePayloadObject = (payload) => {
10
+ if (payload === null) return null;
11
+ try {
12
+ const parsed = JSON.parse(payload);
13
+ if (isStringKeyedObject(parsed)) return parsed;
14
+ } catch {
15
+ return null;
16
+ }
17
+ return null;
18
+ };
19
+ const truncate = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
20
+ const previewOf = (payload) => {
21
+ if (typeof payload !== "string" || payload.length === 0) return null;
22
+ const parsed = parsePayloadObject(payload);
23
+ if (parsed !== null && "text" in parsed) return truncate(String(parsed.text), 60);
24
+ return truncate(payload, 60);
25
+ };
26
+ const toDiagnosticEvent = (row) => {
27
+ const payload = stringOrNull(row.payload);
28
+ return {
29
+ seq: numberOrNull(row.seq),
30
+ ts: numberOrNull(row.ts),
31
+ type: stringOr(row.type, "?"),
32
+ outcome: stringOr(row.outcome, "?"),
33
+ eventId: stringOrNull(row.event_id),
34
+ payload,
35
+ payloadParsed: parsePayloadObject(payload),
36
+ preview: previewOf(row.payload)
37
+ };
38
+ };
39
+ const toDiagnosticConnectionError = (row) => ({
40
+ seq: numberOrNull(row.seq),
41
+ ts: numberOrNull(row.ts),
42
+ type: stringOr(row.type, "?"),
43
+ status: stringOr(row.status, "?"),
44
+ detail: stringOrNull(row.detail)
45
+ });
46
+ const queryRows = (reader, sql, params) => {
47
+ try {
48
+ return reader.query(sql, params);
49
+ } finally {
50
+ reader.close();
51
+ }
52
+ };
53
+ //#endregion
54
+ //#region lib/services/diagnostics/funnel-diagnostics.ts
55
+ const isGatewayStatusResponse = (value) => {
56
+ if (value === null || typeof value !== "object") return false;
57
+ if (!("clients" in value) || !Array.isArray(value.clients)) return false;
58
+ if (!("listeners" in value) || !Array.isArray(value.listeners)) return false;
59
+ return true;
60
+ };
61
+ const connectorOf = (channel, connectorId) => {
62
+ if (connectorId === null) return void 0;
63
+ return channel.connectors?.find((connector) => connector.id === connectorId)?.name;
64
+ };
65
+ const buildDiagnosis = (report) => {
66
+ const rootCause = (report.connectionErrors[report.connectionErrors.length - 1] ?? null)?.detail ?? null;
67
+ if (!report.gateway.running) return {
68
+ status: "error",
69
+ message: "gateway is not running",
70
+ nextActions: ["fnl gateway start"],
71
+ rootCause: null
72
+ };
73
+ const channel = report.channel;
74
+ if (!(report.listeners.length > 0)) return {
75
+ status: "warn",
76
+ message: "no connectors configured on this channel",
77
+ nextActions: [`fnl channels ${channel} connectors add <name> --type=slack ...`],
78
+ rootCause: null
79
+ };
80
+ const allDead = report.listeners.every((l) => !l.alive);
81
+ const someDead = report.listeners.some((l) => !l.alive);
82
+ if (allDead) return {
83
+ status: "error",
84
+ message: "all listeners are dead",
85
+ nextActions: ["fnl doctor --fix", "fnl doctor --fix --aggressive"],
86
+ rootCause
87
+ };
88
+ if (someDead) return {
89
+ status: "warn",
90
+ message: "some listeners are dead",
91
+ nextActions: ["fnl doctor --fix"],
92
+ rootCause
93
+ };
94
+ if (report.claudeClients === 0) return {
95
+ status: "warn",
96
+ message: "no Claude connected to this channel",
97
+ nextActions: [`fnl claude --channel ${channel}`],
98
+ rootCause: null
99
+ };
100
+ if (report.listeners.some((l) => l.errors > 0)) return {
101
+ status: "warn",
102
+ message: "listeners have errors",
103
+ nextActions: ["fnl gateway logs"],
104
+ rootCause
105
+ };
106
+ return {
107
+ status: "ok",
108
+ message: "everything looks healthy",
109
+ nextActions: [],
110
+ rootCause: null
111
+ };
112
+ };
113
+ /**
114
+ * Programmable diagnostics surface — used by both the CLI (fnl debug …) and
115
+ * the MCP tools (fnl_debug, fnl_recent_events, …). Pure read-side, no
116
+ * mutation; pair with FunnelRecovery for self-healing actions.
117
+ */
118
+ var FunnelDiagnostics = class {
119
+ constructor(props) {
120
+ this.props = props;
121
+ Object.freeze(this);
122
+ }
123
+ async diagnose(channelName) {
124
+ const channels = this.props.channels.list();
125
+ const target = channelName ? channels.find((ch) => ch.name === channelName) ?? null : channels[0] ?? null;
126
+ if (!target) return null;
127
+ const gatewayBody = await this.fetchGatewayStatus();
128
+ const store = this.resolveStore();
129
+ return this.buildChannelDiagnosis(target, gatewayBody, store, 5);
130
+ }
131
+ async diagnoseAll() {
132
+ const channels = this.props.channels.list();
133
+ const gatewayBody = await this.fetchGatewayStatus();
134
+ const store = this.resolveStore();
135
+ const reports = await Promise.all(channels.map((ch) => this.buildChannelDiagnosis(ch, gatewayBody, store, 5)));
136
+ const errorChannels = reports.filter((r) => r.diagnosis.status === "error").map((r) => r.channel);
137
+ const warnChannels = reports.filter((r) => r.diagnosis.status === "warn").map((r) => r.channel);
138
+ const okChannels = reports.filter((r) => r.diagnosis.status === "ok").map((r) => r.channel);
139
+ const uniqueActions = [...new Set(reports.flatMap((r) => r.diagnosis.nextActions))];
140
+ return {
141
+ summary: {
142
+ total: reports.length,
143
+ ok: okChannels.length,
144
+ warn: warnChannels.length,
145
+ error: errorChannels.length,
146
+ criticalChannels: errorChannels,
147
+ warnChannels,
148
+ suggestedActions: uniqueActions
149
+ },
150
+ channels: reports
151
+ };
152
+ }
153
+ async recentEvents(channelName, limit = 20) {
154
+ const store = this.resolveStore();
155
+ if (!store) return [];
156
+ const channelId = this.resolveChannelId(channelName);
157
+ if (channelName && !channelId) return [];
158
+ const reader = new ConnectorDiagnosticSqlReader(store);
159
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed ORDER BY seq DESC LIMIT ?", [limit]);
160
+ if (rows instanceof Error) return [];
161
+ return rows.reverse().map(toDiagnosticEvent);
162
+ }
163
+ async droppedEvents(channelName, limit = 20) {
164
+ const store = this.resolveStore();
165
+ if (!store) return [];
166
+ const channelId = this.resolveChannelId(channelName);
167
+ if (channelName && !channelId) return [];
168
+ const reader = new ConnectorDiagnosticSqlReader(store);
169
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE channel_id = ? AND outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [limit]);
170
+ if (rows instanceof Error) return [];
171
+ return rows.reverse().map(toDiagnosticEvent);
172
+ }
173
+ async connectionErrors(channelName, limit = 20) {
174
+ const store = this.resolveStore();
175
+ if (!store) return [];
176
+ const channelId = this.resolveChannelId(channelName);
177
+ if (channelName && !channelId) return [];
178
+ const reader = new ConnectorDiagnosticSqlReader(store);
179
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [limit]);
180
+ if (rows instanceof Error) return [];
181
+ return rows.reverse().map(toDiagnosticConnectionError);
182
+ }
183
+ async replay(channelName, seq) {
184
+ const channel = this.props.channels.list().find((ch) => ch.name === channelName);
185
+ if (!channel) return { state: "not-found" };
186
+ const store = this.resolveStore();
187
+ if (!store) return {
188
+ state: "error",
189
+ reason: "no diagnostic store yet"
190
+ };
191
+ const reader = new ConnectorDiagnosticSqlReader(store);
192
+ const rows = seq !== void 0 ? queryRows(reader, "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND seq = ? LIMIT 1", [channel.id, seq]) : queryRows(reader, "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND outcome LIKE 'emitted%' ORDER BY seq DESC LIMIT 1", [channel.id]);
193
+ if (rows instanceof Error) return {
194
+ state: "error",
195
+ reason: rows.message
196
+ };
197
+ const firstRow = rows[0];
198
+ if (!firstRow) return { state: "not-found" };
199
+ const replaySeq = typeof firstRow.seq === "number" ? firstRow.seq : null;
200
+ const eventId = typeof firstRow.event_id === "string" ? firstRow.event_id : null;
201
+ const connectorId = typeof firstRow.connector_id === "string" ? firstRow.connector_id : null;
202
+ let content = typeof firstRow.payload === "string" ? firstRow.payload : null;
203
+ if ((!content || content.length === 0) && eventId) {
204
+ const rawRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT payload FROM raw WHERE event_id = ? LIMIT 1", [eventId]);
205
+ const rawRow = rawRows instanceof Error ? null : rawRows[0];
206
+ if (rawRow) content = typeof rawRow.payload === "string" ? rawRow.payload : null;
207
+ }
208
+ if (!content) return {
209
+ state: "error",
210
+ reason: "event has no payload to replay"
211
+ };
212
+ const connectorName = connectorOf(channel, connectorId);
213
+ const result = await this.props.publisher.publish(channel.name, {
214
+ content,
215
+ connector: connectorName
216
+ });
217
+ if (result.state === "offline") return { state: "offline" };
218
+ if (result.state === "error") return {
219
+ state: "error",
220
+ reason: result.reason
221
+ };
222
+ return {
223
+ state: "ok",
224
+ seq: replaySeq,
225
+ offset: result.offset,
226
+ preview: content.slice(0, 60)
227
+ };
228
+ }
229
+ resolveStore() {
230
+ const tmpDir = this.props.tmpDir;
231
+ const rawPath = join(tmpDir, "connector-raw.db");
232
+ const processedPath = join(tmpDir, "connector-processed.db");
233
+ const connectionPath = join(tmpDir, "connector-connection.db");
234
+ if (!existsSync(rawPath) || !existsSync(processedPath) || !existsSync(connectionPath)) return null;
235
+ return {
236
+ rawPath,
237
+ processedPath,
238
+ connectionPath
239
+ };
240
+ }
241
+ resolveChannelId(channelName) {
242
+ if (!channelName) return null;
243
+ return this.props.channels.list().find((ch) => ch.name === channelName)?.id ?? null;
244
+ }
245
+ async fetchGatewayStatus() {
246
+ const gatewayStatus = this.props.gateway.getStatus();
247
+ if (!gatewayStatus.running) return null;
248
+ const token = this.props.gatewayToken.read();
249
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
250
+ const res = await fetch(`http://127.0.0.1:${gatewayStatus.port}/status`, { headers }).catch(() => null);
251
+ if (!res || !res.ok) return null;
252
+ const body = await res.json();
253
+ return isGatewayStatusResponse(body) ? body : null;
254
+ }
255
+ async buildChannelDiagnosis(target, gatewayBody, store, eventLimit) {
256
+ const gatewayStatus = this.props.gateway.getStatus();
257
+ const targetName = target.name;
258
+ const baseReport = {
259
+ channel: targetName,
260
+ channelId: target.id,
261
+ gateway: {
262
+ running: gatewayStatus.running,
263
+ pid: gatewayStatus.pid,
264
+ port: gatewayStatus.running ? gatewayStatus.port : null,
265
+ uptimeMs: gatewayBody?.uptimeMs ?? null
266
+ },
267
+ listeners: [],
268
+ claudeClients: 0,
269
+ recentEvents: [],
270
+ connectionErrors: []
271
+ };
272
+ if (gatewayBody) {
273
+ baseReport.listeners = gatewayBody.listeners.filter((l) => l.channelName === targetName).map((l) => ({
274
+ name: l.name,
275
+ type: l.type,
276
+ alive: l.alive,
277
+ events: l.events,
278
+ errors: l.errors,
279
+ lastEventAt: l.lastEventAt
280
+ }));
281
+ baseReport.claudeClients = gatewayBody.clients.filter((cl) => cl.channelName === targetName).length;
282
+ }
283
+ if (store) {
284
+ const evRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [target.id, eventLimit]);
285
+ if (!(evRows instanceof Error)) baseReport.recentEvents = evRows.reverse().map(toDiagnosticEvent);
286
+ const hasDeadListeners = baseReport.listeners.some((l) => !l.alive);
287
+ const hasListenerErrors = baseReport.listeners.some((l) => l.errors > 0);
288
+ if (hasDeadListeners || hasListenerErrors) {
289
+ const errRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT 3", [target.id]);
290
+ if (!(errRows instanceof Error)) baseReport.connectionErrors = errRows.reverse().map(toDiagnosticConnectionError);
291
+ }
292
+ }
293
+ return {
294
+ ...baseReport,
295
+ diagnosis: buildDiagnosis(baseReport)
296
+ };
297
+ }
298
+ };
299
+ //#endregion
300
+ export { toDiagnosticEvent as a, toDiagnosticConnectionError as i, previewOf as n, queryRows as r, FunnelDiagnostics as t };