@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
@@ -0,0 +1,208 @@
1
+ import { z } from "zod";
2
+
3
+ //#region lib/connectors/connector-listener.d.ts
4
+ type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>;
5
+ /**
6
+ * Long-lived event source for one connector.
7
+ *
8
+ * `start()` opens the underlying connection (Slack Socket Mode, Discord
9
+ * Gateway, GH polling, schedule tick) and pushes events through `notify`.
10
+ * `stop()` releases the resources so the supervisor can recreate the listener
11
+ * with new config without restarting the whole gateway. `isAlive()` lets the
12
+ * supervisor periodically health-check and auto-restart dead listeners; the
13
+ * default optimistic implementation is fine for poll/tick-based listeners
14
+ * that self-heal.
15
+ */
16
+ declare abstract class FunnelConnectorListener {
17
+ abstract start(notify: NotifyFn): Promise<void>;
18
+ abstract stop(): Promise<void>;
19
+ isAlive(): boolean;
20
+ }
21
+ //#endregion
22
+ //#region lib/engine/logger/logger.d.ts
23
+ /**
24
+ * Structured logger with three levels and an optional log-file path.
25
+ * Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
26
+ * MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
27
+ */
28
+ declare abstract class FunnelLogger {
29
+ abstract info(message: string, meta?: Record<string, unknown>): void;
30
+ abstract warn(message: string, meta?: Record<string, unknown>): void;
31
+ abstract error(message: string, meta?: Record<string, unknown>): void;
32
+ abstract readonly file: string | null;
33
+ }
34
+ //#endregion
35
+ //#region lib/gateway/connector-diagnostic-log.d.ts
36
+ /**
37
+ * Points in the listener's connection lifecycle. The single source of truth
38
+ * for the value set: the `status` column schema, the `ConnectorConnectionStatus`
39
+ * union, and the runtime Set used to narrow on read-back all derive from this
40
+ * array, so adding a status is a one-line change that cannot drift out of sync.
41
+ *
42
+ * started start() was called
43
+ * connected the socket opened and events can flow
44
+ * disconnected the socket was closed by a stop() call (a clean teardown)
45
+ * auth-failed the token was rejected before the socket opened
46
+ * stopped the listener was fully torn down (always follows a stop(),
47
+ * paired with the disconnected/error that preceded it)
48
+ * error start/stop threw, or Bolt surfaced an error frame — this is
49
+ * also where an unsolicited socket drop shows up when Bolt
50
+ * reports it (an `error` with no following `stopped` means the
51
+ * supervisor recycled the listener, not a clean stop)
52
+ *
53
+ * A connection row is independent of any single inbound event, so it carries
54
+ * no `eventId`. This is how "no notification arrived because the listener
55
+ * never connected (or dropped, or failed auth)" becomes visible: the
56
+ * raw/processed tables only hold events that *did* arrive.
57
+ */
58
+ declare const CONNECTOR_CONNECTION_STATUSES: readonly ["started", "connected", "disconnected", "auth-failed", "stopped", "error"];
59
+ type ConnectorConnectionStatus = (typeof CONNECTOR_CONNECTION_STATUSES)[number];
60
+ /**
61
+ * Rows stored in the diagnostic tables. Connector-agnostic on purpose: `type`
62
+ * carries the listener kind ("slack" | "discord" | "gh" | "schedule") so new
63
+ * connectors land in the same tables without a schema change. `event_id` is
64
+ * the correlation key the listener mints once per inbound event and stamps
65
+ * onto both the raw and processed rows, so the two are joinable even though
66
+ * they live in separate tables with independent `seq` counters.
67
+ *
68
+ * These schemas mirror the stored shape (snake_case columns) the way
69
+ * `FunnelEvent` does for the replay log; they exist for `z.infer` and to
70
+ * document the column set, not as a parse boundary.
71
+ */
72
+ declare const connectorRawEventSchema: z.ZodObject<{
73
+ event_id: z.ZodString;
74
+ type: z.ZodString;
75
+ connector_id: z.ZodNullable<z.ZodString>;
76
+ channel_id: z.ZodNullable<z.ZodString>;
77
+ payload: z.ZodString;
78
+ }, z.core.$strip>;
79
+ type ConnectorRawEvent = z.infer<typeof connectorRawEventSchema>;
80
+ declare const connectorProcessedEventSchema: z.ZodObject<{
81
+ event_id: z.ZodString;
82
+ type: z.ZodString;
83
+ connector_id: z.ZodNullable<z.ZodString>;
84
+ channel_id: z.ZodNullable<z.ZodString>;
85
+ outcome: z.ZodString;
86
+ payload: z.ZodString;
87
+ }, z.core.$strip>;
88
+ type ConnectorProcessedEvent = z.infer<typeof connectorProcessedEventSchema>;
89
+ declare const connectorConnectionEventSchema: z.ZodObject<{
90
+ type: z.ZodString;
91
+ connector_id: z.ZodNullable<z.ZodString>;
92
+ channel_id: z.ZodNullable<z.ZodString>;
93
+ status: z.ZodEnum<{
94
+ error: "error";
95
+ started: "started";
96
+ connected: "connected";
97
+ disconnected: "disconnected";
98
+ "auth-failed": "auth-failed";
99
+ stopped: "stopped";
100
+ }>;
101
+ detail: z.ZodString;
102
+ }, z.core.$strip>;
103
+ type ConnectorConnectionEvent = z.infer<typeof connectorConnectionEventSchema>;
104
+ /** The connector a row belongs to — the axis every diagnostic table shares. */
105
+ type ConnectorRef = {
106
+ type: string;
107
+ connectorId: string | null;
108
+ channelId: string | null;
109
+ };
110
+ /** A row tied to one inbound event, joinable to its twin by `eventId`. */
111
+ type ConnectorEventKeys = ConnectorRef & {
112
+ /** Correlation id shared by the raw and processed rows of the same inbound event. */eventId: string;
113
+ };
114
+ /** One untouched inbound event to persist, before any processing. */
115
+ type ConnectorRawRecord = ConnectorEventKeys & {
116
+ /** The listener's untouched payload, already JSON-stringified by the caller. */payload: string;
117
+ };
118
+ /** The processor's verdict for one inbound event. */
119
+ type ConnectorProcessedRecord = ConnectorEventKeys & {
120
+ /**
121
+ * "emitted" on successful delivery, "emitted:delivery-failed" when the
122
+ * downstream notify threw, or "skip:<reason>" when the processor dropped it.
123
+ */
124
+ outcome: string;
125
+ /**
126
+ * The delivered body (content + meta) for an emitted event. For a skipped
127
+ * event there is no body, so the listener records the event JSON here
128
+ * instead — keeping a skipped row self-describing rather than blank.
129
+ */
130
+ payload: string;
131
+ };
132
+ type ConnectorConnectionRecord = ConnectorRef & {
133
+ status: ConnectorConnectionStatus; /** Free-form context (an error message, a reason) or "" when none. */
134
+ detail: string;
135
+ };
136
+ /**
137
+ * Filters every table query accepts — the read-side mirror of `ConnectorRef`.
138
+ * Each per-table query extends this with its own table's column, the same way
139
+ * each record extends `ConnectorRef`, so neither half of the file treats one
140
+ * table's shape as the base for the others.
141
+ */
142
+ type ConnectorQuery = {
143
+ type?: string;
144
+ connectorId?: string | null;
145
+ channelId?: string | null; /** Cap on returned rows. The most recent matching rows are returned, oldest first. */
146
+ limit?: number;
147
+ };
148
+ type ConnectorRawQuery = ConnectorQuery;
149
+ type ConnectorProcessedQuery = ConnectorQuery & {
150
+ outcome?: string;
151
+ };
152
+ type ConnectorConnectionQuery = ConnectorQuery & {
153
+ status?: ConnectorConnectionStatus;
154
+ };
155
+ /**
156
+ * A stored row, ascending by `seq`. `seq` is per-table (each table counts
157
+ * independently) and is for ordering within one table, not for correlating
158
+ * across them — use `eventId` to join raw and processed.
159
+ */
160
+ type StoredRawEvent = ConnectorRawRecord & {
161
+ seq: number;
162
+ ts: number;
163
+ };
164
+ type StoredProcessedEvent = ConnectorProcessedRecord & {
165
+ seq: number;
166
+ ts: number;
167
+ };
168
+ type StoredConnectionEvent = ConnectorConnectionRecord & {
169
+ seq: number;
170
+ ts: number;
171
+ };
172
+ /**
173
+ * Three-table diagnostic log of everything a connector listener does, so
174
+ * "why was there no notification?" is answerable whichever way it failed:
175
+ * - `raw` — every inbound event, before any filtering, with the listener's
176
+ * untouched payload (the Slack Bolt event, the GH webhook, …)
177
+ * - `processed` — the verdict for that event: `outcome` (emitted, or the
178
+ * reason it was dropped) and, when emitted, the body that was delivered.
179
+ * Shares an `eventId` with its raw row, so the two join into one story.
180
+ * - `connection` — the listener's lifecycle (started, connected, dropped,
181
+ * auth-failed, stopped, errored). This is the half the event tables can't
182
+ * show: an event that never arrived leaves no raw row, but a listener that
183
+ * never connected leaves a `connection` trail that says so.
184
+ *
185
+ * The three are physically separate (independent retention and payload-size
186
+ * policy) so a query never crosses them by accident and a huge raw payload
187
+ * never bloats the verdict or lifecycle trails. None flow to WS clients or the
188
+ * MCP channel — this is a separate store from `FunnelEventLog` (replay) and
189
+ * exists solely for debugging.
190
+ *
191
+ * Implementations:
192
+ * - `SqliteConnectorDiagnosticLog` — the default; survives daemon restarts,
193
+ * bounded by per-table row/age caps.
194
+ * - `MemoryConnectorDiagnosticLog` — an in-process double for tests.
195
+ */
196
+ declare abstract class ConnectorDiagnosticLog {
197
+ abstract recordRaw(record: ConnectorRawRecord): void;
198
+ abstract recordProcessed(record: ConnectorProcessedRecord): void;
199
+ abstract recordConnection(record: ConnectorConnectionRecord): void;
200
+ abstract queryRaw(query: ConnectorRawQuery): StoredRawEvent[];
201
+ abstract queryProcessed(query: ConnectorProcessedQuery): StoredProcessedEvent[];
202
+ abstract queryConnection(query: ConnectorConnectionQuery): StoredConnectionEvent[];
203
+ /** Drop every diagnostic row across all three tables and reclaim the files. */
204
+ abstract clear(): void;
205
+ abstract close(): void;
206
+ }
207
+ //#endregion
208
+ export { NotifyFn as S, connectorConnectionEventSchema as _, ConnectorConnectionStatus as a, FunnelLogger as b, ConnectorProcessedQuery as c, ConnectorRawEvent as d, ConnectorRawQuery as f, StoredRawEvent as g, StoredProcessedEvent as h, ConnectorConnectionRecord as i, ConnectorProcessedRecord as l, StoredConnectionEvent as m, ConnectorConnectionEvent as n, ConnectorDiagnosticLog as o, ConnectorRawRecord as p, ConnectorConnectionQuery as r, ConnectorProcessedEvent as s, CONNECTOR_CONNECTION_STATUSES as t, ConnectorQuery as u, connectorProcessedEventSchema as v, FunnelConnectorListener as x, connectorRawEventSchema as y };
@@ -1,6 +1,6 @@
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";
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, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "../connector-diagnostic-log-OPpPi9V9.js";
4
4
 
5
5
  //#region lib/engine/http/http-client.d.ts
6
6
  type HttpRequest = {
@@ -21,7 +21,8 @@ declare abstract class FunnelHttpClient {
21
21
  //#endregion
22
22
  //#region lib/connectors/discord-adapter.d.ts
23
23
  type Deps$1 = {
24
- config: DiscordConnectorConfig;
24
+ config: DiscordConnectorConfig; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
25
+ env?: NodeJS.ProcessEnv;
25
26
  http?: FunnelHttpClient;
26
27
  };
27
28
  declare class FunnelDiscordAdapter extends FunnelConnectorAdapter {
@@ -60,17 +61,26 @@ declare class FunnelDiscordEventProcessor {
60
61
  //#endregion
61
62
  //#region lib/connectors/discord-listener.d.ts
62
63
  type Deps = {
63
- config: DiscordConnectorConfig;
64
- logger?: FunnelLogger;
64
+ config: DiscordConnectorConfig; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
65
+ channelId?: string; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
66
+ env?: NodeJS.ProcessEnv;
67
+ logger?: FunnelLogger; /** Diagnostic log of inbound events, before and after processing. No-op when absent. */
68
+ diagnosticLog?: ConnectorDiagnosticLog;
65
69
  };
66
70
  declare class FunnelDiscordListener extends FunnelConnectorListener {
67
71
  private readonly config;
72
+ private readonly channelId;
73
+ private readonly env;
68
74
  private readonly logger;
75
+ private readonly diagnosticLog;
69
76
  private client;
70
77
  constructor(deps: Deps);
71
78
  start(notify: NotifyFn): Promise<void>;
72
79
  stop(): Promise<void>;
73
80
  isAlive(): boolean;
81
+ private recordRaw;
82
+ private recordProcessed;
83
+ private recordConnection;
74
84
  }
75
85
  //#endregion
76
86
  export { DiscordConnectorConfig, DiscordInboundMessage, DiscordProcessed, DiscordProcessedEmit, DiscordProcessedSkip, FunnelDiscordAdapter, FunnelDiscordEventProcessor, FunnelDiscordListener, discordConnectorSchema };
@@ -1,2 +1,2 @@
1
- import { i as FunnelDiscordAdapter, n as FunnelDiscordListener, r as FunnelDiscordEventProcessor, t as discordConnectorSchema } from "../discord-connector-schema-CpuI6rmE.js";
1
+ import { i as FunnelDiscordAdapter, n as FunnelDiscordListener, r as FunnelDiscordEventProcessor, t as discordConnectorSchema } from "../discord-connector-schema-RzDvrNE5.js";
2
2
  export { FunnelDiscordAdapter, FunnelDiscordEventProcessor, FunnelDiscordListener, discordConnectorSchema };
@@ -1,6 +1,6 @@
1
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-CXB-q_XC.js";
2
- import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "../logger-B3aXsVcX.js";
3
- import { a as FunnelProcessRunner, n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-Cmi57jvL.js";
1
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-VA6undzc.js";
2
+ import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "../connector-diagnostic-log-OPpPi9V9.js";
3
+ import { a as FunnelProcessRunner, n as ghConnectorSchema, t as GhConnectorConfig } from "../gh-connector-schema-CQmEWzdV.js";
4
4
 
5
5
  //#region lib/connectors/gh-adapter.d.ts
6
6
  type Deps$1 = {
@@ -14,15 +14,19 @@ declare class FunnelGhAdapter extends FunnelConnectorAdapter {
14
14
  //#endregion
15
15
  //#region lib/connectors/gh-listener.d.ts
16
16
  type Deps = {
17
- config: GhConnectorConfig;
17
+ config: GhConnectorConfig; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
18
+ channelId?: string;
18
19
  process?: FunnelProcessRunner;
19
- logger?: FunnelLogger;
20
+ logger?: FunnelLogger; /** Diagnostic log of inbound events, before and after processing. No-op when absent. */
21
+ diagnosticLog?: ConnectorDiagnosticLog;
20
22
  now?: () => Date;
21
23
  };
22
24
  declare class FunnelGhListener extends FunnelConnectorListener {
23
25
  private readonly config;
26
+ private readonly channelId;
24
27
  private readonly process;
25
28
  private readonly logger;
29
+ private readonly diagnosticLog;
26
30
  private readonly now;
27
31
  private readonly seen;
28
32
  private bootstrapped;
@@ -33,6 +37,9 @@ declare class FunnelGhListener extends FunnelConnectorListener {
33
37
  stop(): Promise<void>;
34
38
  isAlive(): boolean;
35
39
  pollOnce(notify: NotifyFn): Promise<void>;
40
+ private recordRaw;
41
+ private recordProcessed;
42
+ private recordConnection;
36
43
  }
37
44
  //#endregion
38
45
  export { FunnelGhAdapter, FunnelGhListener, GhConnectorConfig, ghConnectorSchema };
@@ -1,2 +1,2 @@
1
- import { n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "../gh-connector-schema-CQRIvPpz.js";
1
+ import { n as FunnelGhListener, r as FunnelGhAdapter, t as ghConnectorSchema } from "../gh-connector-schema-eYE4g77K.js";
2
2
  export { FunnelGhAdapter, FunnelGhListener, ghConnectorSchema };
@@ -1,4 +1,4 @@
1
- import { c as ScheduleEntry, d as scheduleEntrySchema, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, r as ScheduleStateStore, s as ScheduleConnectorConfig, t as FunnelScheduleListener, u as scheduleConnectorSchema } from "../schedule-listener-CBYF2bGZ.js";
1
+ import { c as ScheduleEntry, d as scheduleEntrySchema, l as scheduleCatchupPolicySchema, n as ScheduleOnFired, o as ScheduleCatchupPolicy, r as ScheduleStateStore, s as ScheduleConnectorConfig, t as FunnelScheduleListener, u as scheduleConnectorSchema } from "../schedule-listener-3M6WkH1Y.js";
2
2
 
3
3
  //#region lib/connectors/match-cron.d.ts
4
4
  declare const matchCron: (expr: string, date: Date) => boolean;
@@ -1,2 +1,2 @@
1
- import { a as ScheduleStateStore, c as matchCron, i as FunnelScheduleListener, n as scheduleConnectorSchema, r as scheduleEntrySchema, t as scheduleCatchupPolicySchema } from "../schedule-connector-schema-CuCjP7z4.js";
1
+ import { a as ScheduleStateStore, c as matchCron, i as FunnelScheduleListener, n as scheduleConnectorSchema, r as scheduleEntrySchema, t as scheduleCatchupPolicySchema } from "../schedule-connector-schema-CM-sRkac.js";
2
2
  export { FunnelScheduleListener, ScheduleStateStore, matchCron, scheduleCatchupPolicySchema, scheduleConnectorSchema, scheduleEntrySchema };
@@ -1,12 +1,13 @@
1
- import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-CXB-q_XC.js";
2
- 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, t as FunnelSlackListener, u as slackConnectorSchema } from "../slack-listener-DbNCPMqY.js";
1
+ import { n as FunnelConnectorAdapter, t as CallInput } from "../connector-adapter-VA6undzc.js";
2
+ 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, t as FunnelSlackListener, u as SlackConnectorConfig } from "../slack-listener-CLTiOEJw.js";
3
3
 
4
4
  //#region lib/connectors/slack-adapter.d.ts
5
5
  type SlackWebClientLike = {
6
6
  apiCall: (method: string, options?: Record<string, unknown>) => Promise<unknown>;
7
7
  };
8
8
  type Deps = {
9
- config: SlackConnectorConfig;
9
+ config: SlackConnectorConfig; /** Environment used to resolve a `botTokenEnv` reference. Defaults to process.env. */
10
+ env?: NodeJS.ProcessEnv;
10
11
  client?: SlackWebClientLike;
11
12
  };
12
13
  declare class FunnelSlackAdapter extends FunnelConnectorAdapter {
@@ -15,4 +16,4 @@ declare class FunnelSlackAdapter extends FunnelConnectorAdapter {
15
16
  call(input: CallInput): Promise<unknown>;
16
17
  }
17
18
  //#endregion
18
- export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, SlackConnectorConfig, SlackOnAppCreated, SlackPreprocessEvent, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SlackWebClientLike, slackConnectorSchema };
19
+ export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, SlackConnectorConfig, SlackOnAppCreated, SlackPreprocessEvent, SlackProcessed, SlackProcessedEmit, SlackProcessedSkip, SlackRawEvent, SlackSkipReason, SlackWebClientLike, slackConnectorSchema };
@@ -1,2 +1,2 @@
1
- import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "../slack-connector-schema-BWL7dWlY.js";
1
+ import { i as FunnelSlackAdapter, n as FunnelSlackListener, r as FunnelSlackEventProcessor, t as slackConnectorSchema } from "../slack-connector-schema-CHbRJHGp.js";
2
2
  export { FunnelSlackAdapter, FunnelSlackEventProcessor, FunnelSlackListener, slackConnectorSchema };
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  //#region lib/connectors/discord-connector-schema.d.ts
4
+ /**
5
+ * Like slack, a discord connector holds either a literal `botToken` or a
6
+ * `botTokenEnv` reference resolved from the environment at listener start. The
7
+ * reference form keeps the secret in `.env.local` and out of settings.json.
8
+ */
4
9
  declare const discordConnectorSchema: z.ZodObject<{
5
10
  id: z.ZodString;
6
11
  name: z.ZodString;
7
12
  type: z.ZodLiteral<"discord">;
8
- botToken: z.ZodString;
13
+ botToken: z.ZodOptional<z.ZodString>;
14
+ botTokenEnv: z.ZodOptional<z.ZodString>;
9
15
  createdAt: z.ZodOptional<z.ZodString>;
10
16
  updatedAt: z.ZodOptional<z.ZodString>;
11
17
  }, z.core.$strip>;
@@ -1,5 +1,6 @@
1
1
  import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
2
- import { n as FunnelConnectorListener } from "./logger-D1A3_JXV.js";
2
+ import { t as resolveConnectorToken } from "./resolve-connector-token-Ch6XWMJM.js";
3
+ import { n as FunnelConnectorListener } from "./logger-Czli2OKh.js";
3
4
  import { Client, GatewayIntentBits, Partials } from "discord.js";
4
5
  import { z } from "zod";
5
6
  //#region lib/engine/http/http-client.ts
@@ -34,7 +35,12 @@ var FunnelDiscordAdapter = class extends FunnelConnectorAdapter {
34
35
  http;
35
36
  constructor(deps) {
36
37
  super();
37
- this.token = deps.config.botToken;
38
+ this.token = resolveConnectorToken({
39
+ literal: deps.config.botToken,
40
+ envVar: deps.config.botTokenEnv,
41
+ env: deps.env ?? process.env,
42
+ label: `${deps.config.name}.botToken`
43
+ });
38
44
  this.http = deps.http ?? defaultHttp;
39
45
  Object.freeze(this);
40
46
  }
@@ -84,14 +90,21 @@ var FunnelDiscordEventProcessor = class {
84
90
  //#region lib/connectors/discord-listener.ts
85
91
  var FunnelDiscordListener = class extends FunnelConnectorListener {
86
92
  config;
93
+ channelId;
94
+ env;
87
95
  logger;
96
+ diagnosticLog;
88
97
  client = null;
89
98
  constructor(deps) {
90
99
  super();
91
100
  this.config = deps.config;
101
+ this.channelId = deps.channelId ?? null;
102
+ this.env = deps.env ?? process.env;
92
103
  this.logger = deps.logger;
104
+ this.diagnosticLog = deps.diagnosticLog;
93
105
  }
94
106
  async start(notify) {
107
+ this.recordConnection("started", "");
95
108
  const client = new Client({
96
109
  intents: [
97
110
  GatewayIntentBits.Guilds,
@@ -113,23 +126,30 @@ var FunnelDiscordListener = class extends FunnelConnectorListener {
113
126
  ownUserId,
114
127
  mentioned: String(mentionedUserIds.includes(ownUserId))
115
128
  });
129
+ const rawEvent = message.toJSON();
130
+ const eventId = crypto.randomUUID();
131
+ this.recordRaw(eventId, rawEvent);
116
132
  const result = new FunnelDiscordEventProcessor({ ownUserId }).process({
117
133
  authorId: message.author.id,
118
134
  authorIsBot: message.author.bot,
119
135
  channelId: message.channelId,
120
136
  guildId: message.guildId,
121
137
  mentionedUserIds,
122
- raw: message.toJSON()
138
+ raw: rawEvent
123
139
  });
124
140
  if (result.skip) {
141
+ this.recordProcessed(eventId, rawEvent, "skip:bot", "");
125
142
  this.logger?.info("discord skip", { reason: "bot author" });
126
143
  return;
127
144
  }
128
145
  try {
129
146
  await notify(result.content, result.meta);
130
147
  } catch (error) {
131
- this.logger?.error("discord notify error", { error: error instanceof Error ? error.message : String(error) });
148
+ this.recordProcessed(eventId, rawEvent, "emitted:delivery-failed", result.content);
149
+ this.logger?.error("discord notify error", { error: messageOf(error) });
150
+ return;
132
151
  }
152
+ this.recordProcessed(eventId, rawEvent, "emitted", result.content);
133
153
  });
134
154
  client.on("ready", (readyClient) => {
135
155
  this.logger?.info("discord ready", {
@@ -139,32 +159,85 @@ var FunnelDiscordListener = class extends FunnelConnectorListener {
139
159
  });
140
160
  });
141
161
  client.on("error", (error) => {
142
- this.logger?.error("discord client error", { error: error instanceof Error ? error.message : String(error) });
162
+ this.recordConnection("error", messageOf(error));
163
+ this.logger?.error("discord client error", { error: messageOf(error) });
143
164
  });
144
- await client.login(this.config.botToken);
165
+ try {
166
+ await client.login(resolveConnectorToken({
167
+ literal: this.config.botToken,
168
+ envVar: this.config.botTokenEnv,
169
+ env: this.env,
170
+ label: `${this.config.name}.botToken`
171
+ }));
172
+ } catch (error) {
173
+ this.recordConnection("error", messageOf(error));
174
+ throw error;
175
+ }
145
176
  this.client = client;
177
+ this.recordConnection("connected", "");
146
178
  }
147
179
  async stop() {
148
180
  if (!this.client) return;
149
181
  try {
150
182
  await this.client.destroy();
183
+ this.recordConnection("disconnected", "");
151
184
  } catch (error) {
152
- this.logger?.error("discord stop error", { error: error instanceof Error ? error.message : String(error) });
185
+ this.recordConnection("error", messageOf(error));
186
+ this.logger?.error("discord stop error", { error: messageOf(error) });
153
187
  } finally {
154
188
  this.client = null;
189
+ this.recordConnection("stopped", "");
155
190
  }
156
191
  }
157
192
  isAlive() {
158
193
  return this.client !== null;
159
194
  }
195
+ recordRaw(eventId, rawEvent) {
196
+ this.diagnosticLog?.recordRaw({
197
+ eventId,
198
+ type: "discord",
199
+ connectorId: this.config.id,
200
+ channelId: this.channelId,
201
+ payload: JSON.stringify(rawEvent)
202
+ });
203
+ }
204
+ recordProcessed(eventId, rawEvent, outcome, content) {
205
+ this.diagnosticLog?.recordProcessed({
206
+ eventId,
207
+ type: "discord",
208
+ connectorId: this.config.id,
209
+ channelId: this.channelId,
210
+ outcome,
211
+ payload: content || JSON.stringify(rawEvent)
212
+ });
213
+ }
214
+ recordConnection(status, detail) {
215
+ this.diagnosticLog?.recordConnection({
216
+ type: "discord",
217
+ connectorId: this.config.id,
218
+ channelId: this.channelId,
219
+ status,
220
+ detail
221
+ });
222
+ }
223
+ };
224
+ const messageOf = (error) => {
225
+ return error instanceof Error ? error.message : String(error);
160
226
  };
161
227
  //#endregion
162
228
  //#region lib/connectors/discord-connector-schema.ts
229
+ /**
230
+ * Like slack, a discord connector holds either a literal `botToken` or a
231
+ * `botTokenEnv` reference resolved from the environment at listener start. The
232
+ * reference form keeps the secret in `.env.local` and out of settings.json.
233
+ */
163
234
  const discordConnectorSchema = z.object({
164
235
  id: z.string(),
165
236
  name: z.string(),
166
237
  type: z.literal("discord"),
167
- botToken: z.string().min(10),
238
+ botToken: z.string().min(10).optional(),
239
+ /** Name of the env var holding the bot token, resolved at listener start. */
240
+ botTokenEnv: z.string().optional(),
168
241
  createdAt: z.string().datetime().optional(),
169
242
  updatedAt: z.string().datetime().optional()
170
243
  });