@interactive-inc/claude-funnel 0.26.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +786 -717
- package/dist/connector-diagnostic-log-Clb2sCcz.d.ts +206 -0
- package/dist/connectors/discord.d.ts +16 -6
- package/dist/connectors/discord.js +1 -1
- package/dist/connectors/gh.d.ts +12 -5
- package/dist/connectors/gh.js +1 -1
- package/dist/connectors/schedule.d.ts +1 -1
- package/dist/connectors/schedule.js +1 -1
- package/dist/connectors/slack.d.ts +5 -4
- package/dist/connectors/slack.js +1 -1
- package/dist/{discord-connector-schema-Dww2I4zH.d.ts → discord-connector-schema-Df_McRJI.d.ts} +7 -1
- package/dist/{discord-connector-schema-CpuI6rmE.js → discord-connector-schema-RzDvrNE5.js} +81 -8
- package/dist/gateway/daemon.js +225 -225
- package/dist/{gh-connector-schema-CQRIvPpz.js → gh-connector-schema-eYE4g77K.js} +51 -3
- package/dist/index.d.ts +213 -38
- package/dist/index.js +727 -103
- package/dist/resolve-connector-token-Ch6XWMJM.js +22 -0
- package/dist/{schedule-connector-schema-CuCjP7z4.js → schedule-connector-schema-CM-sRkac.js} +53 -3
- package/dist/{schedule-listener-CBYF2bGZ.d.ts → schedule-listener-C2-KqHQc.d.ts} +10 -3
- package/dist/{slack-connector-schema-BWL7dWlY.js → slack-connector-schema-CHbRJHGp.js} +140 -19
- package/dist/slack-listener-BMknoyVr.d.ts +112 -0
- package/package.json +1 -1
- package/dist/logger-B3aXsVcX.d.ts +0 -33
- package/dist/slack-listener-DbNCPMqY.d.ts +0 -77
- /package/dist/{connector-adapter-CXB-q_XC.d.ts → connector-adapter-VA6undzc.d.ts} +0 -0
- /package/dist/{gh-connector-schema-Cmi57jvL.d.ts → gh-connector-schema-CQmEWzdV.d.ts} +0 -0
- /package/dist/{logger-D1A3_JXV.js → logger-Czli2OKh.js} +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region lib/connectors/resolve-connector-token.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a connector token from either a literal value or the name of an env
|
|
4
|
+
* var. A connector config carries one or the other per slot (see
|
|
5
|
+
* slack-connector-schema): literals are inlined into settings.json, references
|
|
6
|
+
* keep the secret in the environment (`.env.local`) and out of settings.json.
|
|
7
|
+
*
|
|
8
|
+
* Errors loudly when neither yields a value — a misconfigured connector should
|
|
9
|
+
* fail at listener start, not connect with an empty token and silently never
|
|
10
|
+
* receive events.
|
|
11
|
+
*/
|
|
12
|
+
const resolveConnectorToken = (props) => {
|
|
13
|
+
if (props.literal !== void 0 && props.literal !== "") return props.literal;
|
|
14
|
+
if (props.envVar !== void 0 && props.envVar !== "") {
|
|
15
|
+
const fromEnv = props.env[props.envVar];
|
|
16
|
+
if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
|
|
17
|
+
throw new Error(`${props.label} references env var "${props.envVar}" but it is not set in the environment`);
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`${props.label} has neither a literal token nor an env var reference`);
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { resolveConnectorToken as t };
|
package/dist/{schedule-connector-schema-CuCjP7z4.js → schedule-connector-schema-CM-sRkac.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as FunnelConnectorListener } from "./logger-
|
|
1
|
+
import { n as FunnelConnectorListener } from "./logger-Czli2OKh.js";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -157,7 +157,9 @@ const MAX_CATCHUP_MINUTES = 1440;
|
|
|
157
157
|
var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
158
158
|
config;
|
|
159
159
|
lastFiredStore;
|
|
160
|
+
channelId;
|
|
160
161
|
logger;
|
|
162
|
+
diagnosticLog;
|
|
161
163
|
now;
|
|
162
164
|
onFired;
|
|
163
165
|
timer = null;
|
|
@@ -166,12 +168,15 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
166
168
|
super();
|
|
167
169
|
this.config = deps.config;
|
|
168
170
|
this.lastFiredStore = deps.lastFiredStore;
|
|
171
|
+
this.channelId = deps.channelId ?? null;
|
|
169
172
|
this.logger = deps.logger;
|
|
173
|
+
this.diagnosticLog = deps.diagnosticLog;
|
|
170
174
|
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
171
175
|
this.onFired = deps.onFired ?? null;
|
|
172
176
|
}
|
|
173
177
|
async start(notify) {
|
|
174
178
|
this.stopped = false;
|
|
179
|
+
this.recordConnection("started", "");
|
|
175
180
|
const scheduleNext = () => {
|
|
176
181
|
if (this.stopped) return;
|
|
177
182
|
const date = this.now();
|
|
@@ -192,6 +197,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
192
197
|
clearTimeout(this.timer);
|
|
193
198
|
this.timer = null;
|
|
194
199
|
}
|
|
200
|
+
this.recordConnection("stopped", "");
|
|
195
201
|
}
|
|
196
202
|
isAlive() {
|
|
197
203
|
return !this.stopped && this.timer !== null;
|
|
@@ -243,7 +249,15 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
243
249
|
catchup_policy: entry.catchupPolicy
|
|
244
250
|
};
|
|
245
251
|
if (catchup) meta.catchup = "true";
|
|
246
|
-
|
|
252
|
+
const eventId = `${entry.id}@${firedAt.toISOString()}`;
|
|
253
|
+
this.recordRaw(eventId, entry, firedAt, catchup);
|
|
254
|
+
try {
|
|
255
|
+
await notify(entry.prompt, meta);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
this.recordProcessed(eventId, entry, "emitted:delivery-failed");
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
this.recordProcessed(eventId, entry, "emitted");
|
|
247
261
|
if (this.onFired) try {
|
|
248
262
|
await this.onFired(entry, firedAt);
|
|
249
263
|
} catch (error) {
|
|
@@ -289,11 +303,13 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
289
303
|
return matches;
|
|
290
304
|
}
|
|
291
305
|
logInvalidCron(entry, error) {
|
|
306
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
307
|
+
this.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
|
|
292
308
|
this.logger?.error("invalid cron expression in schedule", {
|
|
293
309
|
connector: this.config.name,
|
|
294
310
|
id: entry.id,
|
|
295
311
|
cron: entry.cron,
|
|
296
|
-
error:
|
|
312
|
+
error: message
|
|
297
313
|
});
|
|
298
314
|
}
|
|
299
315
|
truncateToMinute(date) {
|
|
@@ -301,6 +317,40 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
|
301
317
|
copy.setSeconds(0, 0);
|
|
302
318
|
return copy;
|
|
303
319
|
}
|
|
320
|
+
recordRaw(eventId, entry, firedAt, catchup) {
|
|
321
|
+
this.diagnosticLog?.recordRaw({
|
|
322
|
+
eventId,
|
|
323
|
+
type: "schedule",
|
|
324
|
+
connectorId: this.config.id,
|
|
325
|
+
channelId: this.channelId,
|
|
326
|
+
payload: JSON.stringify({
|
|
327
|
+
schedule_id: entry.id,
|
|
328
|
+
cron: entry.cron,
|
|
329
|
+
prompt: entry.prompt,
|
|
330
|
+
fired_at: firedAt.toISOString(),
|
|
331
|
+
catchup
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
recordProcessed(eventId, entry, outcome) {
|
|
336
|
+
this.diagnosticLog?.recordProcessed({
|
|
337
|
+
eventId,
|
|
338
|
+
type: "schedule",
|
|
339
|
+
connectorId: this.config.id,
|
|
340
|
+
channelId: this.channelId,
|
|
341
|
+
outcome,
|
|
342
|
+
payload: entry.prompt
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
recordConnection(status, detail) {
|
|
346
|
+
this.diagnosticLog?.recordConnection({
|
|
347
|
+
type: "schedule",
|
|
348
|
+
connectorId: this.config.id,
|
|
349
|
+
channelId: this.channelId,
|
|
350
|
+
status,
|
|
351
|
+
detail
|
|
352
|
+
});
|
|
353
|
+
}
|
|
304
354
|
};
|
|
305
355
|
//#endregion
|
|
306
356
|
//#region lib/connectors/schedule-connector-schema.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-Clb2sCcz.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region lib/connectors/schedule-connector-schema.d.ts
|
|
@@ -95,8 +95,10 @@ declare class ScheduleStateStore {
|
|
|
95
95
|
type ScheduleOnFired = (entry: ScheduleEntry, firedAt: Date) => void | Promise<void>;
|
|
96
96
|
type Deps = {
|
|
97
97
|
config: ScheduleConnectorConfig;
|
|
98
|
-
lastFiredStore: ScheduleStateStore;
|
|
99
|
-
|
|
98
|
+
lastFiredStore: ScheduleStateStore; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
|
|
99
|
+
channelId?: string;
|
|
100
|
+
logger?: FunnelLogger; /** Diagnostic log of fired entries and lifecycle. No-op when absent. */
|
|
101
|
+
diagnosticLog?: ConnectorDiagnosticLog;
|
|
100
102
|
now?: () => Date;
|
|
101
103
|
/**
|
|
102
104
|
* Invoked after a schedule entry fires successfully. Use to remove one-shot
|
|
@@ -108,7 +110,9 @@ type Deps = {
|
|
|
108
110
|
declare class FunnelScheduleListener extends FunnelConnectorListener {
|
|
109
111
|
private readonly config;
|
|
110
112
|
private readonly lastFiredStore;
|
|
113
|
+
private readonly channelId;
|
|
111
114
|
private readonly logger;
|
|
115
|
+
private readonly diagnosticLog;
|
|
112
116
|
private readonly now;
|
|
113
117
|
private readonly onFired;
|
|
114
118
|
private timer;
|
|
@@ -124,6 +128,9 @@ declare class FunnelScheduleListener extends FunnelConnectorListener {
|
|
|
124
128
|
private findAllMatches;
|
|
125
129
|
private logInvalidCron;
|
|
126
130
|
private truncateToMinute;
|
|
131
|
+
private recordRaw;
|
|
132
|
+
private recordProcessed;
|
|
133
|
+
private recordConnection;
|
|
127
134
|
}
|
|
128
135
|
//#endregion
|
|
129
136
|
export { FunnelFileSystem as a, ScheduleEntry as c, scheduleEntrySchema as d, FileStat as i, scheduleCatchupPolicySchema as l, ScheduleOnFired as n, ScheduleCatchupPolicy as o, ScheduleStateStore as r, ScheduleConnectorConfig as s, FunnelScheduleListener as t, scheduleConnectorSchema as u };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
|
|
2
|
-
import {
|
|
2
|
+
import { t as resolveConnectorToken } from "./resolve-connector-token-Ch6XWMJM.js";
|
|
3
|
+
import { n as FunnelConnectorListener } from "./logger-Czli2OKh.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { WebClient } from "@slack/web-api";
|
|
5
6
|
import { App, LogLevel } from "@slack/bolt";
|
|
@@ -31,7 +32,13 @@ var FunnelSlackAdapter = class extends FunnelConnectorAdapter {
|
|
|
31
32
|
client;
|
|
32
33
|
constructor(deps) {
|
|
33
34
|
super();
|
|
34
|
-
|
|
35
|
+
const botToken = resolveConnectorToken({
|
|
36
|
+
literal: deps.config.botToken,
|
|
37
|
+
envVar: deps.config.botTokenEnv,
|
|
38
|
+
env: deps.env ?? process.env,
|
|
39
|
+
label: `${deps.config.name}.botToken`
|
|
40
|
+
});
|
|
41
|
+
this.client = deps.client ?? new WebClient(botToken);
|
|
35
42
|
Object.freeze(this);
|
|
36
43
|
}
|
|
37
44
|
async call(input) {
|
|
@@ -157,19 +164,34 @@ var FunnelSlackEventProcessor = class {
|
|
|
157
164
|
}
|
|
158
165
|
process(event) {
|
|
159
166
|
const eventType = getString(event, "type");
|
|
160
|
-
if (!eventType || !ALLOWED_EVENTS.has(eventType)) return {
|
|
167
|
+
if (!eventType || !ALLOWED_EVENTS.has(eventType)) return {
|
|
168
|
+
skip: true,
|
|
169
|
+
reason: "skip:type"
|
|
170
|
+
};
|
|
161
171
|
const subtype = getString(event, "subtype");
|
|
162
|
-
if (!ALLOWED_SUBTYPES.has(subtype)) return {
|
|
172
|
+
if (!ALLOWED_SUBTYPES.has(subtype)) return {
|
|
173
|
+
skip: true,
|
|
174
|
+
reason: "skip:subtype"
|
|
175
|
+
};
|
|
163
176
|
const channelId = getString(event, "channel") ?? "";
|
|
164
177
|
const dedupKey = `${channelId}:${getString(event, "event_ts") ?? getString(event, "ts") ?? ""}`;
|
|
165
178
|
const now = this.now();
|
|
166
|
-
if (this.dedup.has(dedupKey)) return {
|
|
179
|
+
if (this.dedup.has(dedupKey)) return {
|
|
180
|
+
skip: true,
|
|
181
|
+
reason: "skip:dedup"
|
|
182
|
+
};
|
|
167
183
|
this.dedup.set(dedupKey, now);
|
|
168
184
|
for (const key of this.dedup.keys()) if ((this.dedup.get(key) ?? 0) < now - DEDUP_WINDOW) this.dedup.delete(key);
|
|
169
185
|
const userId = getString(event, "user");
|
|
170
186
|
const botId = getString(event, "bot_id");
|
|
171
|
-
if (userId === this.ownBotUserId) return {
|
|
172
|
-
|
|
187
|
+
if (userId === this.ownBotUserId) return {
|
|
188
|
+
skip: true,
|
|
189
|
+
reason: "skip:self-user"
|
|
190
|
+
};
|
|
191
|
+
if (botId === this.ownBotId) return {
|
|
192
|
+
skip: true,
|
|
193
|
+
reason: "skip:self-bot"
|
|
194
|
+
};
|
|
173
195
|
const mentioned = (getString(event, "text") ?? "").includes(`<@${this.ownBotUserId}>`);
|
|
174
196
|
const threadTs = getString(event, "thread_ts") ?? getString(event, "ts") ?? "";
|
|
175
197
|
const emitted = this.minify ? minifySlackEvent(event) : event;
|
|
@@ -194,25 +216,49 @@ var FunnelSlackEventProcessor = class {
|
|
|
194
216
|
const middlewareArgsSchema = z.object({ event: z.record(z.string(), z.unknown()).optional() });
|
|
195
217
|
var FunnelSlackListener = class extends FunnelConnectorListener {
|
|
196
218
|
config;
|
|
219
|
+
channelId;
|
|
220
|
+
env;
|
|
197
221
|
logger;
|
|
222
|
+
diagnosticLog;
|
|
198
223
|
onAppCreated;
|
|
199
224
|
preprocessEvent;
|
|
200
225
|
app = null;
|
|
201
226
|
constructor(deps) {
|
|
202
227
|
super();
|
|
203
228
|
this.config = deps.config;
|
|
229
|
+
this.channelId = deps.channelId ?? null;
|
|
230
|
+
this.env = deps.env ?? process.env;
|
|
204
231
|
this.logger = deps.logger;
|
|
232
|
+
this.diagnosticLog = deps.diagnosticLog;
|
|
205
233
|
this.onAppCreated = deps.onAppCreated ?? null;
|
|
206
234
|
this.preprocessEvent = deps.preprocessEvent ?? null;
|
|
207
235
|
}
|
|
208
236
|
async start(notify) {
|
|
237
|
+
this.recordConnection("started", "");
|
|
238
|
+
const botToken = resolveConnectorToken({
|
|
239
|
+
literal: this.config.botToken,
|
|
240
|
+
envVar: this.config.botTokenEnv,
|
|
241
|
+
env: this.env,
|
|
242
|
+
label: `${this.config.name}.botToken`
|
|
243
|
+
});
|
|
209
244
|
const app = new App({
|
|
210
|
-
token:
|
|
211
|
-
appToken:
|
|
245
|
+
token: botToken,
|
|
246
|
+
appToken: resolveConnectorToken({
|
|
247
|
+
literal: this.config.appToken,
|
|
248
|
+
envVar: this.config.appTokenEnv,
|
|
249
|
+
env: this.env,
|
|
250
|
+
label: `${this.config.name}.appToken`
|
|
251
|
+
}),
|
|
212
252
|
socketMode: true,
|
|
213
253
|
logLevel: LogLevel.ERROR
|
|
214
254
|
});
|
|
215
|
-
|
|
255
|
+
let authResult;
|
|
256
|
+
try {
|
|
257
|
+
authResult = await app.client.auth.test({ token: botToken });
|
|
258
|
+
} catch (error) {
|
|
259
|
+
this.recordConnection("auth-failed", messageOf(error));
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
216
262
|
const processor = new FunnelSlackEventProcessor({
|
|
217
263
|
ownBotUserId: authResult.user_id ?? "",
|
|
218
264
|
ownBotId: authResult.bot_id ?? "",
|
|
@@ -226,14 +272,28 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
|
|
|
226
272
|
return;
|
|
227
273
|
}
|
|
228
274
|
const rawEvent = parsed.data.event;
|
|
275
|
+
const eventId = crypto.randomUUID();
|
|
276
|
+
this.recordRaw(eventId, rawEvent);
|
|
229
277
|
const event = preprocess ? preprocess(rawEvent) : rawEvent;
|
|
230
|
-
if (event === null)
|
|
278
|
+
if (event === null) {
|
|
279
|
+
this.recordProcessed(eventId, rawEvent, "skip:preprocess", "");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
231
282
|
const result = processor.process(event);
|
|
232
|
-
if (result.skip)
|
|
233
|
-
|
|
283
|
+
if (result.skip) {
|
|
284
|
+
this.recordProcessed(eventId, event, result.reason, "");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await notify(result.content, result.meta);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
this.recordProcessed(eventId, event, "emitted:delivery-failed", result.content);
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
this.recordProcessed(eventId, event, "emitted", result.content);
|
|
234
294
|
if (result.shouldReact) try {
|
|
235
295
|
await app.client.reactions.add({
|
|
236
|
-
token:
|
|
296
|
+
token: botToken,
|
|
237
297
|
channel: result.channel,
|
|
238
298
|
timestamp: result.timestamp,
|
|
239
299
|
name: "eyes"
|
|
@@ -241,34 +301,95 @@ var FunnelSlackListener = class extends FunnelConnectorListener {
|
|
|
241
301
|
} catch {}
|
|
242
302
|
});
|
|
243
303
|
app.error(async (error) => {
|
|
244
|
-
|
|
304
|
+
const message = messageOf(error);
|
|
305
|
+
this.recordConnection("error", message);
|
|
306
|
+
this.logger?.error("Slack error", { error: message });
|
|
245
307
|
});
|
|
246
308
|
if (this.onAppCreated) await this.onAppCreated(app);
|
|
247
|
-
|
|
309
|
+
try {
|
|
310
|
+
await app.start();
|
|
311
|
+
} catch (error) {
|
|
312
|
+
this.recordConnection("error", messageOf(error));
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
248
315
|
this.app = app;
|
|
316
|
+
this.recordConnection("connected", "");
|
|
249
317
|
}
|
|
250
318
|
async stop() {
|
|
251
319
|
if (!this.app) return;
|
|
252
320
|
try {
|
|
253
321
|
await this.app.stop();
|
|
322
|
+
this.recordConnection("disconnected", "");
|
|
254
323
|
} catch (error) {
|
|
255
|
-
this.
|
|
324
|
+
this.recordConnection("error", messageOf(error));
|
|
325
|
+
this.logger?.error("Slack stop error", { error: messageOf(error) });
|
|
256
326
|
} finally {
|
|
257
327
|
this.app = null;
|
|
328
|
+
this.recordConnection("stopped", "");
|
|
258
329
|
}
|
|
259
330
|
}
|
|
260
331
|
isAlive() {
|
|
261
332
|
return this.app !== null;
|
|
262
333
|
}
|
|
334
|
+
recordRaw(eventId, event) {
|
|
335
|
+
this.diagnosticLog?.recordRaw({
|
|
336
|
+
eventId,
|
|
337
|
+
type: "slack",
|
|
338
|
+
connectorId: this.config.id,
|
|
339
|
+
channelId: this.channelId,
|
|
340
|
+
payload: JSON.stringify(event)
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
recordProcessed(eventId, event, outcome, content) {
|
|
344
|
+
this.diagnosticLog?.recordProcessed({
|
|
345
|
+
eventId,
|
|
346
|
+
type: "slack",
|
|
347
|
+
connectorId: this.config.id,
|
|
348
|
+
channelId: this.channelId,
|
|
349
|
+
outcome,
|
|
350
|
+
payload: content || JSON.stringify(event)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
recordConnection(status, detail) {
|
|
354
|
+
this.diagnosticLog?.recordConnection({
|
|
355
|
+
type: "slack",
|
|
356
|
+
connectorId: this.config.id,
|
|
357
|
+
channelId: this.channelId,
|
|
358
|
+
status,
|
|
359
|
+
detail
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
const messageOf = (error) => {
|
|
364
|
+
return error instanceof Error ? error.message : String(error);
|
|
263
365
|
};
|
|
264
366
|
//#endregion
|
|
265
367
|
//#region lib/connectors/slack-connector-schema.ts
|
|
368
|
+
/**
|
|
369
|
+
* A slack connector resolves its tokens one of two ways, set at sync time:
|
|
370
|
+
*
|
|
371
|
+
* - literal: `botToken` / `appToken` hold the real `xoxb-`/`xapp-` secret
|
|
372
|
+
* (funnel.json gave a literal, or a `fnl channels` command did).
|
|
373
|
+
* - by reference: `botTokenEnv` / `appTokenEnv` hold the *name* of an env var
|
|
374
|
+
* (funnel.json used `env: { botToken: "SLACK_BOT_TOKEN" }`). The secret
|
|
375
|
+
* never lands in settings.json; the listener resolves it from the
|
|
376
|
+
* environment at start. This keeps repo-local launches' tokens in
|
|
377
|
+
* `.env.local` only.
|
|
378
|
+
*
|
|
379
|
+
* Both are optional at the schema level (a discriminated-union member can't
|
|
380
|
+
* carry a cross-field refine); the listener requires exactly one resolved
|
|
381
|
+
* token per slot and errors loudly otherwise.
|
|
382
|
+
*/
|
|
266
383
|
const slackConnectorSchema = z.object({
|
|
267
384
|
id: z.string(),
|
|
268
385
|
name: z.string(),
|
|
269
386
|
type: z.literal("slack"),
|
|
270
|
-
botToken: z.string().startsWith("xoxb-"),
|
|
271
|
-
appToken: z.string().startsWith("xapp-"),
|
|
387
|
+
botToken: z.string().startsWith("xoxb-").optional(),
|
|
388
|
+
appToken: z.string().startsWith("xapp-").optional(),
|
|
389
|
+
/** Name of the env var holding the bot token, resolved at listener start. */
|
|
390
|
+
botTokenEnv: z.string().optional(),
|
|
391
|
+
/** Name of the env var holding the app token, resolved at listener start. */
|
|
392
|
+
appTokenEnv: z.string().optional(),
|
|
272
393
|
minify: z.boolean().default(true),
|
|
273
394
|
createdAt: z.string().datetime().optional(),
|
|
274
395
|
updatedAt: z.string().datetime().optional()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { S as NotifyFn, b as FunnelLogger, o as ConnectorDiagnosticLog, x as FunnelConnectorListener } from "./connector-diagnostic-log-Clb2sCcz.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { App } from "@slack/bolt";
|
|
4
|
+
|
|
5
|
+
//#region lib/connectors/slack-connector-schema.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A slack connector resolves its tokens one of two ways, set at sync time:
|
|
8
|
+
*
|
|
9
|
+
* - literal: `botToken` / `appToken` hold the real `xoxb-`/`xapp-` secret
|
|
10
|
+
* (funnel.json gave a literal, or a `fnl channels` command did).
|
|
11
|
+
* - by reference: `botTokenEnv` / `appTokenEnv` hold the *name* of an env var
|
|
12
|
+
* (funnel.json used `env: { botToken: "SLACK_BOT_TOKEN" }`). The secret
|
|
13
|
+
* never lands in settings.json; the listener resolves it from the
|
|
14
|
+
* environment at start. This keeps repo-local launches' tokens in
|
|
15
|
+
* `.env.local` only.
|
|
16
|
+
*
|
|
17
|
+
* Both are optional at the schema level (a discriminated-union member can't
|
|
18
|
+
* carry a cross-field refine); the listener requires exactly one resolved
|
|
19
|
+
* token per slot and errors loudly otherwise.
|
|
20
|
+
*/
|
|
21
|
+
declare const slackConnectorSchema: z.ZodObject<{
|
|
22
|
+
id: z.ZodString;
|
|
23
|
+
name: z.ZodString;
|
|
24
|
+
type: z.ZodLiteral<"slack">;
|
|
25
|
+
botToken: z.ZodOptional<z.ZodString>;
|
|
26
|
+
appToken: z.ZodOptional<z.ZodString>;
|
|
27
|
+
botTokenEnv: z.ZodOptional<z.ZodString>;
|
|
28
|
+
appTokenEnv: z.ZodOptional<z.ZodString>;
|
|
29
|
+
minify: z.ZodDefault<z.ZodBoolean>;
|
|
30
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
31
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region lib/connectors/slack-event-processor.d.ts
|
|
36
|
+
type SlackRawEvent = Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Why the processor dropped an event. Mirrored verbatim into the diagnostic
|
|
39
|
+
* log's processed `outcome` column so "Slack delivered it but no notification arrived" is
|
|
40
|
+
* traceable to the exact gate that dropped it. The listener may additionally
|
|
41
|
+
* record `skip:preprocess` for events a host preprocessor dropped before the
|
|
42
|
+
* processor ran — that gate is outside this type.
|
|
43
|
+
*/
|
|
44
|
+
type SlackSkipReason = "skip:type" | "skip:subtype" | "skip:dedup" | "skip:self-user" | "skip:self-bot";
|
|
45
|
+
type SlackProcessedSkip = {
|
|
46
|
+
skip: true;
|
|
47
|
+
reason: SlackSkipReason;
|
|
48
|
+
};
|
|
49
|
+
type SlackProcessedEmit = {
|
|
50
|
+
skip: false;
|
|
51
|
+
content: string;
|
|
52
|
+
meta: Record<string, string>;
|
|
53
|
+
shouldReact: boolean;
|
|
54
|
+
channel: string;
|
|
55
|
+
timestamp: string;
|
|
56
|
+
};
|
|
57
|
+
type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
|
|
58
|
+
type Props = {
|
|
59
|
+
ownBotUserId: string;
|
|
60
|
+
ownBotId: string;
|
|
61
|
+
minify?: boolean;
|
|
62
|
+
now?: () => number;
|
|
63
|
+
};
|
|
64
|
+
declare class FunnelSlackEventProcessor {
|
|
65
|
+
private readonly ownBotUserId;
|
|
66
|
+
private readonly ownBotId;
|
|
67
|
+
private readonly minify;
|
|
68
|
+
private readonly now;
|
|
69
|
+
private readonly dedup;
|
|
70
|
+
constructor(props: Props);
|
|
71
|
+
process(event: SlackRawEvent): SlackProcessed;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region lib/connectors/slack-listener.d.ts
|
|
75
|
+
type SlackOnAppCreated = (app: App) => void | Promise<void>;
|
|
76
|
+
type SlackPreprocessEvent = (event: SlackRawEvent) => SlackRawEvent | null;
|
|
77
|
+
type Deps = {
|
|
78
|
+
config: SlackConnectorConfig; /** Funnel channel uuid this connector lives under; stamped onto diagnostic-log rows. */
|
|
79
|
+
channelId?: string; /** Environment used to resolve `botTokenEnv`/`appTokenEnv` references. Defaults to process.env. */
|
|
80
|
+
env?: NodeJS.ProcessEnv;
|
|
81
|
+
logger?: FunnelLogger; /** Diagnostic log of inbound events, before and after processing. No-op when absent. */
|
|
82
|
+
diagnosticLog?: ConnectorDiagnosticLog;
|
|
83
|
+
/**
|
|
84
|
+
* Invoked after the Bolt App is constructed, before it starts.
|
|
85
|
+
* Use to attach app.action handlers, custom middleware, etc.
|
|
86
|
+
*/
|
|
87
|
+
onAppCreated?: SlackOnAppCreated;
|
|
88
|
+
/**
|
|
89
|
+
* Transform or drop the raw Slack event before the built-in processor sees it.
|
|
90
|
+
* Return null to drop the event entirely.
|
|
91
|
+
*/
|
|
92
|
+
preprocessEvent?: SlackPreprocessEvent;
|
|
93
|
+
};
|
|
94
|
+
declare class FunnelSlackListener extends FunnelConnectorListener {
|
|
95
|
+
private readonly config;
|
|
96
|
+
private readonly channelId;
|
|
97
|
+
private readonly env;
|
|
98
|
+
private readonly logger;
|
|
99
|
+
private readonly diagnosticLog;
|
|
100
|
+
private readonly onAppCreated;
|
|
101
|
+
private readonly preprocessEvent;
|
|
102
|
+
private app;
|
|
103
|
+
constructor(deps: Deps);
|
|
104
|
+
start(notify: NotifyFn): Promise<void>;
|
|
105
|
+
stop(): Promise<void>;
|
|
106
|
+
isAlive(): boolean;
|
|
107
|
+
private recordRaw;
|
|
108
|
+
private recordProcessed;
|
|
109
|
+
private recordConnection;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { SlackProcessed as a, SlackRawEvent as c, slackConnectorSchema as d, FunnelSlackEventProcessor as i, SlackSkipReason as l, SlackOnAppCreated as n, SlackProcessedEmit as o, SlackPreprocessEvent as r, SlackProcessedSkip as s, FunnelSlackListener as t, SlackConnectorConfig as u };
|
package/package.json
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
//#region lib/connectors/connector-listener.d.ts
|
|
2
|
-
type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>;
|
|
3
|
-
/**
|
|
4
|
-
* Long-lived event source for one connector.
|
|
5
|
-
*
|
|
6
|
-
* `start()` opens the underlying connection (Slack Socket Mode, Discord
|
|
7
|
-
* Gateway, GH polling, schedule tick) and pushes events through `notify`.
|
|
8
|
-
* `stop()` releases the resources so the supervisor can recreate the listener
|
|
9
|
-
* with new config without restarting the whole gateway. `isAlive()` lets the
|
|
10
|
-
* supervisor periodically health-check and auto-restart dead listeners; the
|
|
11
|
-
* default optimistic implementation is fine for poll/tick-based listeners
|
|
12
|
-
* that self-heal.
|
|
13
|
-
*/
|
|
14
|
-
declare abstract class FunnelConnectorListener {
|
|
15
|
-
abstract start(notify: NotifyFn): Promise<void>;
|
|
16
|
-
abstract stop(): Promise<void>;
|
|
17
|
-
isAlive(): boolean;
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
//#region lib/engine/logger/logger.d.ts
|
|
21
|
-
/**
|
|
22
|
-
* Structured logger with three levels and an optional log-file path.
|
|
23
|
-
* Defaults to NodeFunnelLogger (appends to `<os.tmpdir()>/funnel/funnel.log`);
|
|
24
|
-
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
25
|
-
*/
|
|
26
|
-
declare abstract class FunnelLogger {
|
|
27
|
-
abstract info(message: string, meta?: Record<string, unknown>): void;
|
|
28
|
-
abstract warn(message: string, meta?: Record<string, unknown>): void;
|
|
29
|
-
abstract error(message: string, meta?: Record<string, unknown>): void;
|
|
30
|
-
abstract readonly file: string | null;
|
|
31
|
-
}
|
|
32
|
-
//#endregion
|
|
33
|
-
export { FunnelConnectorListener as n, NotifyFn as r, FunnelLogger as t };
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { n as FunnelConnectorListener, r as NotifyFn, t as FunnelLogger } from "./logger-B3aXsVcX.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { App } from "@slack/bolt";
|
|
4
|
-
|
|
5
|
-
//#region lib/connectors/slack-connector-schema.d.ts
|
|
6
|
-
declare const slackConnectorSchema: z.ZodObject<{
|
|
7
|
-
id: z.ZodString;
|
|
8
|
-
name: z.ZodString;
|
|
9
|
-
type: z.ZodLiteral<"slack">;
|
|
10
|
-
botToken: z.ZodString;
|
|
11
|
-
appToken: z.ZodString;
|
|
12
|
-
minify: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
-
createdAt: z.ZodOptional<z.ZodString>;
|
|
14
|
-
updatedAt: z.ZodOptional<z.ZodString>;
|
|
15
|
-
}, z.core.$strip>;
|
|
16
|
-
type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region lib/connectors/slack-event-processor.d.ts
|
|
19
|
-
type SlackRawEvent = Record<string, unknown>;
|
|
20
|
-
type SlackProcessedSkip = {
|
|
21
|
-
skip: true;
|
|
22
|
-
};
|
|
23
|
-
type SlackProcessedEmit = {
|
|
24
|
-
skip: false;
|
|
25
|
-
content: string;
|
|
26
|
-
meta: Record<string, string>;
|
|
27
|
-
shouldReact: boolean;
|
|
28
|
-
channel: string;
|
|
29
|
-
timestamp: string;
|
|
30
|
-
};
|
|
31
|
-
type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
|
|
32
|
-
type Props = {
|
|
33
|
-
ownBotUserId: string;
|
|
34
|
-
ownBotId: string;
|
|
35
|
-
minify?: boolean;
|
|
36
|
-
now?: () => number;
|
|
37
|
-
};
|
|
38
|
-
declare class FunnelSlackEventProcessor {
|
|
39
|
-
private readonly ownBotUserId;
|
|
40
|
-
private readonly ownBotId;
|
|
41
|
-
private readonly minify;
|
|
42
|
-
private readonly now;
|
|
43
|
-
private readonly dedup;
|
|
44
|
-
constructor(props: Props);
|
|
45
|
-
process(event: SlackRawEvent): SlackProcessed;
|
|
46
|
-
}
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region lib/connectors/slack-listener.d.ts
|
|
49
|
-
type SlackOnAppCreated = (app: App) => void | Promise<void>;
|
|
50
|
-
type SlackPreprocessEvent = (event: SlackRawEvent) => SlackRawEvent | null;
|
|
51
|
-
type Deps = {
|
|
52
|
-
config: SlackConnectorConfig;
|
|
53
|
-
logger?: FunnelLogger;
|
|
54
|
-
/**
|
|
55
|
-
* Invoked after the Bolt App is constructed, before it starts.
|
|
56
|
-
* Use to attach app.action handlers, custom middleware, etc.
|
|
57
|
-
*/
|
|
58
|
-
onAppCreated?: SlackOnAppCreated;
|
|
59
|
-
/**
|
|
60
|
-
* Transform or drop the raw Slack event before the built-in processor sees it.
|
|
61
|
-
* Return null to drop the event entirely.
|
|
62
|
-
*/
|
|
63
|
-
preprocessEvent?: SlackPreprocessEvent;
|
|
64
|
-
};
|
|
65
|
-
declare class FunnelSlackListener extends FunnelConnectorListener {
|
|
66
|
-
private readonly config;
|
|
67
|
-
private readonly logger;
|
|
68
|
-
private readonly onAppCreated;
|
|
69
|
-
private readonly preprocessEvent;
|
|
70
|
-
private app;
|
|
71
|
-
constructor(deps: Deps);
|
|
72
|
-
start(notify: NotifyFn): Promise<void>;
|
|
73
|
-
stop(): Promise<void>;
|
|
74
|
-
isAlive(): boolean;
|
|
75
|
-
}
|
|
76
|
-
//#endregion
|
|
77
|
-
export { SlackProcessed as a, SlackRawEvent as c, FunnelSlackEventProcessor as i, SlackConnectorConfig as l, SlackOnAppCreated as n, SlackProcessedEmit as o, SlackPreprocessEvent as r, SlackProcessedSkip as s, FunnelSlackListener as t, slackConnectorSchema as u };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|