@interactive-inc/claude-funnel 0.60.1 → 0.63.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/README.md +2 -2
- package/dist/bin.js +428 -761
- package/dist/{channels-2g_BU1N0.d.ts → channels-B8RQPrVq.d.ts} +17 -16
- package/dist/claude.d.ts +5 -7
- package/dist/claude.js +143 -36
- package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-ClEEbuW3.d.ts} +50 -11
- package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
- package/dist/connectors/discord.d.ts +31 -37
- package/dist/connectors/discord.js +3 -3
- package/dist/connectors/gh.d.ts +37 -33
- package/dist/connectors/gh.js +3 -3
- package/dist/connectors/schedule.d.ts +9 -57
- package/dist/connectors/schedule.js +3 -3
- package/dist/connectors/slack.d.ts +71 -131
- package/dist/connectors/slack.js +4 -3
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -1
- package/dist/discord-connector-DIFkYBbi.js +250 -0
- package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
- package/dist/docs.js +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/{file-process-guard-C_PLxfUX.d.ts → file-process-guard-DGHxALfI.d.ts} +6 -6
- package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
- package/dist/flume-source-listener-Dim5szHG.d.ts +133 -0
- package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
- package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
- package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
- package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
- package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
- package/dist/funnel-error-0t1MK1R6.js +75 -0
- package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
- package/dist/gateway/daemon.js +167 -527
- package/dist/gateway.d.ts +3 -3
- package/dist/gateway.js +3 -3
- package/dist/gh-connector-BUGCOEWS.js +187 -0
- package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
- package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
- package/dist/{index-CgY8NdMz.d.ts → index-DxRikYmu.d.ts} +37 -19
- package/dist/index.d.ts +182 -22
- package/dist/index.js +363 -173
- package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
- package/dist/local-config.d.ts +39 -2
- package/dist/local-config.js +53 -2
- package/dist/logger.js +1 -1
- package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
- package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-DP_YV9xX.d.ts} +30 -3
- package/dist/node-file-system-BOXIHW_Q.js +174 -0
- package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
- package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/recovery.js +1 -1
- package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
- package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
- package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
- package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
- package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
- package/dist/slack-connector-BU86fIge.js +359 -0
- package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
- package/dist/slack-event-processor-xFDG3US0.js +176 -0
- package/dist/slot-fields-D-pvMgTK.js +249 -0
- package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
- package/dist/{yaml-render-93pX7EF7.js → yaml-render--J1_3BSA.js} +25 -21
- package/package.json +2 -4
- package/dist/discord-connector-BL36yvbL.js +0 -250
- package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
- package/dist/gh-connector-DpiixfQZ.js +0 -226
- package/dist/http-client-oICicjuO.d.ts +0 -18
- package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
- package/dist/memory-token-prompter-CZde7e6y.js +0 -61
- package/dist/node-file-system-Blr8pAir.js +0 -48
- package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
- package/dist/slack-connector-DQIFPdBF.js +0 -484
- package/dist/slot-fields-CMoRpwuy.js +0 -45
- /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
- /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
- /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
- /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
- /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
- /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
- /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
- /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
- /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
- /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
- /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
- /package/dist/{slack-connector-schema-C1zEf4TG.js → slack-connector-schema-Dem8to4P.js} +0 -0
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { i as FunnelTokenPrompter, o as LOCAL_CONFIG_FILENAME } from "./local-config-json-schema-JyLqOQNX.js";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
//#region lib/services/local-config/local-config-writer.ts
|
|
4
|
-
const isRecord = (value) => {
|
|
5
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
-
};
|
|
7
|
-
const withIdFirst = (config, id) => {
|
|
8
|
-
const ordered = {};
|
|
9
|
-
if (config.$schema !== void 0) ordered.$schema = config.$schema;
|
|
10
|
-
ordered.id = id;
|
|
11
|
-
for (const key of Object.keys(config)) {
|
|
12
|
-
if (key === "$schema" || key === "id") continue;
|
|
13
|
-
ordered[key] = config[key];
|
|
14
|
-
}
|
|
15
|
-
return ordered;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* The one path that mutates the repo-committed funnel.json, and it only ever
|
|
19
|
-
* inserts `id`. On first launch a repo has no `id`; funnel generates one and
|
|
20
|
-
* writes it back here so future launches resolve the same `~/.funnel/projects/<id>/`.
|
|
21
|
-
* Idempotent — a no-op once `id` is present. Kept separate from the read-only
|
|
22
|
-
* FunnelLocalConfig so reads stay side-effect free.
|
|
23
|
-
*/
|
|
24
|
-
var FunnelLocalConfigWriter = class {
|
|
25
|
-
fs;
|
|
26
|
-
constructor(deps) {
|
|
27
|
-
this.fs = deps.fs;
|
|
28
|
-
Object.freeze(this);
|
|
29
|
-
}
|
|
30
|
-
ensureId(cwd, id) {
|
|
31
|
-
const path = join(cwd, LOCAL_CONFIG_FILENAME);
|
|
32
|
-
if (!this.fs.existsSync(path)) return;
|
|
33
|
-
const parsed = JSON.parse(this.fs.readFileSync(path));
|
|
34
|
-
if (!isRecord(parsed)) return;
|
|
35
|
-
if (typeof parsed.id === "string" && parsed.id !== "") return;
|
|
36
|
-
const ordered = withIdFirst(parsed, id);
|
|
37
|
-
this.fs.writeFileSync(path, `${JSON.stringify(ordered, null, 2)}\n`);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region lib/engine/token-prompter/memory-token-prompter.ts
|
|
42
|
-
/**
|
|
43
|
-
* Pre-seeded answers keyed by prompt label. Tests configure the map up front;
|
|
44
|
-
* unmapped labels throw so the test surfaces unexpected prompts loudly.
|
|
45
|
-
*/
|
|
46
|
-
var MemoryFunnelTokenPrompter = class extends FunnelTokenPrompter {
|
|
47
|
-
answers;
|
|
48
|
-
asked = [];
|
|
49
|
-
constructor(props = {}) {
|
|
50
|
-
super();
|
|
51
|
-
this.answers = new Map(Object.entries(props.answers ?? {}));
|
|
52
|
-
}
|
|
53
|
-
async promptSecret(label) {
|
|
54
|
-
this.asked.push(label);
|
|
55
|
-
const answer = this.answers.get(label);
|
|
56
|
-
if (answer === void 0) throw new Error(`no answer seeded for prompt "${label}"`);
|
|
57
|
-
return answer;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
//#endregion
|
|
61
|
-
export { FunnelLocalConfigWriter as n, MemoryFunnelTokenPrompter as t };
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { t as FunnelFileSystem } from "./file-system-Wvzc2ePY.js";
|
|
2
|
-
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
//#region lib/engine/fs/node-file-system.ts
|
|
4
|
-
const SECRET_MODE = 384;
|
|
5
|
-
var NodeFunnelFileSystem = class extends FunnelFileSystem {
|
|
6
|
-
constructor() {
|
|
7
|
-
super();
|
|
8
|
-
Object.freeze(this);
|
|
9
|
-
}
|
|
10
|
-
existsSync(path) {
|
|
11
|
-
return existsSync(path);
|
|
12
|
-
}
|
|
13
|
-
readFileSync(path) {
|
|
14
|
-
return readFileSync(path, "utf-8");
|
|
15
|
-
}
|
|
16
|
-
writeFileSync(path, data) {
|
|
17
|
-
writeFileSync(path, data);
|
|
18
|
-
}
|
|
19
|
-
writeSecretFileSync(path, data) {
|
|
20
|
-
writeFileSync(path, data, { mode: SECRET_MODE });
|
|
21
|
-
try {
|
|
22
|
-
chmodSync(path, SECRET_MODE);
|
|
23
|
-
} catch {}
|
|
24
|
-
}
|
|
25
|
-
appendFileSync(path, data) {
|
|
26
|
-
appendFileSync(path, data);
|
|
27
|
-
}
|
|
28
|
-
unlink(path) {
|
|
29
|
-
try {
|
|
30
|
-
unlinkSync(path);
|
|
31
|
-
} catch {}
|
|
32
|
-
}
|
|
33
|
-
mkdirSync(path, options) {
|
|
34
|
-
mkdirSync(path, { recursive: options?.recursive ?? false });
|
|
35
|
-
}
|
|
36
|
-
readdirSync(path) {
|
|
37
|
-
return readdirSync(path);
|
|
38
|
-
}
|
|
39
|
-
statSync(path) {
|
|
40
|
-
const stat = statSync(path);
|
|
41
|
-
return {
|
|
42
|
-
mtimeMs: stat.mtimeMs,
|
|
43
|
-
mode: stat.mode & 511
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
//#endregion
|
|
48
|
-
export { NodeFunnelFileSystem as t };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { a as Settings } from "./settings-schema-D1xcOqRu.js";
|
|
2
|
-
|
|
3
|
-
//#region lib/engine/id/id-generator.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
|
|
6
|
-
* MemoryFunnelIdGenerator emits `<prefix>-1, <prefix>-2, ...` for deterministic tests.
|
|
7
|
-
*/
|
|
8
|
-
declare abstract class FunnelIdGenerator {
|
|
9
|
-
abstract generate(): string;
|
|
10
|
-
}
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region lib/engine/settings/settings-reader.d.ts
|
|
13
|
-
declare abstract class FunnelSettingsReader {
|
|
14
|
-
abstract read(): Settings;
|
|
15
|
-
abstract write(settings: Settings): void;
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
export { FunnelIdGenerator as n, FunnelSettingsReader as t };
|
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
import { t as slackConnectorSchema } from "./slack-connector-schema-C1zEf4TG.js";
|
|
2
|
-
import { t as FunnelConnectorAdapter } from "./connector-adapter-DU9Rvyec.js";
|
|
3
|
-
import { t as FunnelConnectorListener } from "./connector-listener-DR3aKOuK.js";
|
|
4
|
-
import { t as errorMessageOf } from "./error-message-of-Byi4y0Uf.js";
|
|
5
|
-
import { n as resolveConnectorToken, t as slotFields } from "./slot-fields-CMoRpwuy.js";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { WebClient } from "@slack/web-api";
|
|
8
|
-
import { App, LogLevel, SocketModeReceiver } from "@slack/bolt";
|
|
9
|
-
//#region lib/engine/connectors/slack-adapter.ts
|
|
10
|
-
const toRecord = (value) => {
|
|
11
|
-
const result = {};
|
|
12
|
-
for (const [key, val] of Object.entries(value)) result[key] = val;
|
|
13
|
-
return result;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Recognises errors that @slack/web-api throws for Slack-side API failures
|
|
17
|
-
* (e.g. `cant_delete_message`, `channel_not_found`, rate limits). Every such
|
|
18
|
-
* error carries `code: "slack_webapi_*"` and a `data` field holding the raw
|
|
19
|
-
* Slack response with `ok: false`. We unwrap to that response so the caller
|
|
20
|
-
* receives a structured failure instead of having the gateway translate it
|
|
21
|
-
* into an opaque HTTP 500.
|
|
22
|
-
*/
|
|
23
|
-
const slackErrorResponse = (error) => {
|
|
24
|
-
if (!error || typeof error !== "object") return null;
|
|
25
|
-
if (!("code" in error)) return null;
|
|
26
|
-
const code = error.code;
|
|
27
|
-
if (typeof code !== "string" || !code.startsWith("slack_webapi_")) return null;
|
|
28
|
-
if (!("data" in error)) return null;
|
|
29
|
-
const data = error.data;
|
|
30
|
-
if (!data || typeof data !== "object") return null;
|
|
31
|
-
return data;
|
|
32
|
-
};
|
|
33
|
-
var FunnelSlackAdapter = class extends FunnelConnectorAdapter {
|
|
34
|
-
client;
|
|
35
|
-
constructor(deps) {
|
|
36
|
-
super();
|
|
37
|
-
const botToken = resolveConnectorToken({
|
|
38
|
-
literal: deps.config.botToken,
|
|
39
|
-
envVar: deps.config.botTokenEnv,
|
|
40
|
-
env: deps.env ?? process.env,
|
|
41
|
-
label: `${deps.config.name}.botToken`
|
|
42
|
-
});
|
|
43
|
-
this.client = deps.client ?? new WebClient(botToken);
|
|
44
|
-
Object.freeze(this);
|
|
45
|
-
}
|
|
46
|
-
async call(input) {
|
|
47
|
-
const body = input.body !== null && typeof input.body === "object" ? toRecord(input.body) : {};
|
|
48
|
-
try {
|
|
49
|
-
return await this.client.apiCall(input.path, body);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
const slackResponse = slackErrorResponse(error);
|
|
52
|
-
if (slackResponse) return slackResponse;
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async postMessage(props) {
|
|
57
|
-
return this.call({
|
|
58
|
-
method: "post",
|
|
59
|
-
path: "chat.postMessage",
|
|
60
|
-
body: {
|
|
61
|
-
channel: props.channel,
|
|
62
|
-
text: props.text,
|
|
63
|
-
...props.threadTs ? { thread_ts: props.threadTs } : {}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
async addReaction(props) {
|
|
68
|
-
return this.call({
|
|
69
|
-
method: "post",
|
|
70
|
-
path: "reactions.add",
|
|
71
|
-
body: {
|
|
72
|
-
channel: props.channel,
|
|
73
|
-
timestamp: props.timestamp,
|
|
74
|
-
name: props.name
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
async removeReaction(props) {
|
|
79
|
-
return this.call({
|
|
80
|
-
method: "post",
|
|
81
|
-
path: "reactions.remove",
|
|
82
|
-
body: {
|
|
83
|
-
channel: props.channel,
|
|
84
|
-
timestamp: props.timestamp,
|
|
85
|
-
name: props.name
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region lib/engine/connectors/minify-slack-event.ts
|
|
92
|
-
const TOP_LEVEL_KEYS = [
|
|
93
|
-
"type",
|
|
94
|
-
"subtype",
|
|
95
|
-
"user",
|
|
96
|
-
"bot_id",
|
|
97
|
-
"text",
|
|
98
|
-
"ts",
|
|
99
|
-
"thread_ts",
|
|
100
|
-
"channel",
|
|
101
|
-
"channel_type",
|
|
102
|
-
"files",
|
|
103
|
-
"attachments"
|
|
104
|
-
];
|
|
105
|
-
const FILE_KEYS = [
|
|
106
|
-
"id",
|
|
107
|
-
"name",
|
|
108
|
-
"mimetype",
|
|
109
|
-
"filetype",
|
|
110
|
-
"size",
|
|
111
|
-
"url_private",
|
|
112
|
-
"permalink"
|
|
113
|
-
];
|
|
114
|
-
const ATTACHMENT_KEYS = [
|
|
115
|
-
"title",
|
|
116
|
-
"text",
|
|
117
|
-
"fallback"
|
|
118
|
-
];
|
|
119
|
-
const isRecord = (value) => {
|
|
120
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
121
|
-
};
|
|
122
|
-
const pickDefined = (source, keys) => {
|
|
123
|
-
const picked = {};
|
|
124
|
-
for (const key of keys) if (source[key] !== void 0) picked[key] = source[key];
|
|
125
|
-
return picked;
|
|
126
|
-
};
|
|
127
|
-
const hasThumbOrPreviewKey = (file) => {
|
|
128
|
-
return Object.keys(file).some((key) => key.startsWith("thumb") || key.startsWith("preview"));
|
|
129
|
-
};
|
|
130
|
-
const minifyFile = (file) => {
|
|
131
|
-
if (!isRecord(file)) return file;
|
|
132
|
-
const minified = pickDefined(file, FILE_KEYS);
|
|
133
|
-
if (hasThumbOrPreviewKey(file)) minified._funnel_omitted = ["thumb_*"];
|
|
134
|
-
return minified;
|
|
135
|
-
};
|
|
136
|
-
const flattenRichText = (node) => {
|
|
137
|
-
if (!isRecord(node)) return "";
|
|
138
|
-
const text = node.text;
|
|
139
|
-
if (typeof text === "string") return text;
|
|
140
|
-
const elements = node.elements;
|
|
141
|
-
if (!Array.isArray(elements)) return "";
|
|
142
|
-
return elements.map(flattenRichText).join("");
|
|
143
|
-
};
|
|
144
|
-
const flattenTableRow = (row) => {
|
|
145
|
-
if (!Array.isArray(row)) return "";
|
|
146
|
-
return row.map(flattenRichText).join(" ");
|
|
147
|
-
};
|
|
148
|
-
const flattenBlock = (block) => {
|
|
149
|
-
if (!isRecord(block)) return "";
|
|
150
|
-
if (block.type === "table" && Array.isArray(block.rows)) return block.rows.map(flattenTableRow).join("\n");
|
|
151
|
-
return flattenRichText(block);
|
|
152
|
-
};
|
|
153
|
-
const flattenBlocks = (blocks) => {
|
|
154
|
-
return blocks.map(flattenBlock).filter((line) => line.length > 0).join("\n");
|
|
155
|
-
};
|
|
156
|
-
const minifyAttachment = (attachment) => {
|
|
157
|
-
if (!isRecord(attachment)) return attachment;
|
|
158
|
-
const minified = pickDefined(attachment, ATTACHMENT_KEYS);
|
|
159
|
-
const blocks = attachment.blocks;
|
|
160
|
-
if (Array.isArray(blocks)) {
|
|
161
|
-
const flattened = flattenBlocks(blocks);
|
|
162
|
-
const existingText = typeof minified.text === "string" ? minified.text : "";
|
|
163
|
-
minified.text = existingText ? `${existingText}\n${flattened}` : flattened;
|
|
164
|
-
minified._funnel_omitted = ["blocks"];
|
|
165
|
-
}
|
|
166
|
-
return minified;
|
|
167
|
-
};
|
|
168
|
-
const minifySlackEvent = (event) => {
|
|
169
|
-
const minified = pickDefined(event, TOP_LEVEL_KEYS);
|
|
170
|
-
if (Array.isArray(minified.files)) minified.files = minified.files.map(minifyFile);
|
|
171
|
-
if (Array.isArray(minified.attachments)) minified.attachments = minified.attachments.map(minifyAttachment);
|
|
172
|
-
return minified;
|
|
173
|
-
};
|
|
174
|
-
//#endregion
|
|
175
|
-
//#region lib/engine/connectors/slack-event-processor.ts
|
|
176
|
-
const ALLOWED_EVENTS = new Set(["message", "app_mention"]);
|
|
177
|
-
const ALLOWED_SUBTYPES = new Set([
|
|
178
|
-
void 0,
|
|
179
|
-
"thread_broadcast",
|
|
180
|
-
"bot_message",
|
|
181
|
-
"file_share"
|
|
182
|
-
]);
|
|
183
|
-
const DEDUP_WINDOW = 1e4;
|
|
184
|
-
const getString = (event, key) => {
|
|
185
|
-
const value = event[key];
|
|
186
|
-
return typeof value === "string" ? value : void 0;
|
|
187
|
-
};
|
|
188
|
-
var FunnelSlackEventProcessor = class {
|
|
189
|
-
ownBotUserId;
|
|
190
|
-
ownBotId;
|
|
191
|
-
minify;
|
|
192
|
-
now;
|
|
193
|
-
dedup = /* @__PURE__ */ new Map();
|
|
194
|
-
constructor(props) {
|
|
195
|
-
this.ownBotUserId = props.ownBotUserId;
|
|
196
|
-
this.ownBotId = props.ownBotId;
|
|
197
|
-
this.minify = props.minify ?? true;
|
|
198
|
-
this.now = props.now ?? (() => Date.now());
|
|
199
|
-
}
|
|
200
|
-
process(event) {
|
|
201
|
-
const eventType = getString(event, "type");
|
|
202
|
-
if (!eventType || !ALLOWED_EVENTS.has(eventType)) return {
|
|
203
|
-
skip: true,
|
|
204
|
-
reason: "skip:type"
|
|
205
|
-
};
|
|
206
|
-
const subtype = getString(event, "subtype");
|
|
207
|
-
if (!ALLOWED_SUBTYPES.has(subtype)) return {
|
|
208
|
-
skip: true,
|
|
209
|
-
reason: "skip:subtype"
|
|
210
|
-
};
|
|
211
|
-
const channelId = getString(event, "channel") ?? "";
|
|
212
|
-
const dedupKey = `${channelId}:${getString(event, "event_ts") ?? getString(event, "ts") ?? ""}`;
|
|
213
|
-
const now = this.now();
|
|
214
|
-
if (this.dedup.has(dedupKey)) return {
|
|
215
|
-
skip: true,
|
|
216
|
-
reason: "skip:dedup"
|
|
217
|
-
};
|
|
218
|
-
this.dedup.set(dedupKey, now);
|
|
219
|
-
for (const key of this.dedup.keys()) if ((this.dedup.get(key) ?? 0) < now - DEDUP_WINDOW) this.dedup.delete(key);
|
|
220
|
-
const userId = getString(event, "user");
|
|
221
|
-
const botId = getString(event, "bot_id");
|
|
222
|
-
if (userId === this.ownBotUserId) return {
|
|
223
|
-
skip: true,
|
|
224
|
-
reason: "skip:self-user"
|
|
225
|
-
};
|
|
226
|
-
if (botId === this.ownBotId) return {
|
|
227
|
-
skip: true,
|
|
228
|
-
reason: "skip:self-bot"
|
|
229
|
-
};
|
|
230
|
-
const rawText = getString(event, "text") ?? "";
|
|
231
|
-
const mentioned = rawText.includes(`<@${this.ownBotUserId}>`);
|
|
232
|
-
const threadTs = getString(event, "thread_ts") ?? getString(event, "ts") ?? "";
|
|
233
|
-
const ts = getString(event, "ts") ?? "";
|
|
234
|
-
const source = eventType === "app_mention" ? "app_mention" : "message";
|
|
235
|
-
const emitted = this.minify ? minifySlackEvent(event) : event;
|
|
236
|
-
return {
|
|
237
|
-
skip: false,
|
|
238
|
-
event: {
|
|
239
|
-
kind: "message",
|
|
240
|
-
channel: channelId,
|
|
241
|
-
user: userId ?? "",
|
|
242
|
-
rawText,
|
|
243
|
-
text: stripMention(rawText, this.ownBotUserId),
|
|
244
|
-
threadTs,
|
|
245
|
-
ts,
|
|
246
|
-
isThreadRoot: threadTs === ts,
|
|
247
|
-
mentioned,
|
|
248
|
-
source
|
|
249
|
-
},
|
|
250
|
-
content: JSON.stringify(emitted),
|
|
251
|
-
meta: {
|
|
252
|
-
event_type: "slack",
|
|
253
|
-
channel_id: channelId,
|
|
254
|
-
user_id: userId ?? "",
|
|
255
|
-
mentioned: String(mentioned),
|
|
256
|
-
thread_ts: threadTs
|
|
257
|
-
},
|
|
258
|
-
shouldReact: mentioned,
|
|
259
|
-
channel: channelId,
|
|
260
|
-
timestamp: ts
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
const stripMention = (text, botUserId) => text.replace(new RegExp(`<@${botUserId}>`, "g"), "").trim();
|
|
265
|
-
//#endregion
|
|
266
|
-
//#region lib/engine/connectors/slack-listener.ts
|
|
267
|
-
const middlewareArgsSchema = z.object({ event: z.record(z.string(), z.unknown()).optional() });
|
|
268
|
-
var FunnelSlackListener = class extends FunnelConnectorListener {
|
|
269
|
-
config;
|
|
270
|
-
channelId;
|
|
271
|
-
env;
|
|
272
|
-
logger;
|
|
273
|
-
diagnosticLog;
|
|
274
|
-
onAppCreated;
|
|
275
|
-
preprocessEvent;
|
|
276
|
-
app = null;
|
|
277
|
-
connected = false;
|
|
278
|
-
constructor(deps) {
|
|
279
|
-
super();
|
|
280
|
-
this.config = deps.config;
|
|
281
|
-
this.channelId = deps.channelId ?? null;
|
|
282
|
-
this.env = deps.env ?? process.env;
|
|
283
|
-
this.logger = deps.logger;
|
|
284
|
-
this.diagnosticLog = deps.diagnosticLog;
|
|
285
|
-
this.onAppCreated = deps.onAppCreated ?? null;
|
|
286
|
-
this.preprocessEvent = deps.preprocessEvent ?? null;
|
|
287
|
-
}
|
|
288
|
-
async start(notify) {
|
|
289
|
-
this.recordConnection("started", "");
|
|
290
|
-
const botToken = resolveConnectorToken({
|
|
291
|
-
literal: this.config.botToken,
|
|
292
|
-
envVar: this.config.botTokenEnv,
|
|
293
|
-
env: this.env,
|
|
294
|
-
label: `${this.config.name}.botToken`
|
|
295
|
-
});
|
|
296
|
-
const receiver = new SocketModeReceiver({
|
|
297
|
-
appToken: resolveConnectorToken({
|
|
298
|
-
literal: this.config.appToken,
|
|
299
|
-
envVar: this.config.appTokenEnv,
|
|
300
|
-
env: this.env,
|
|
301
|
-
label: `${this.config.name}.appToken`
|
|
302
|
-
}),
|
|
303
|
-
logLevel: LogLevel.ERROR,
|
|
304
|
-
autoReconnectEnabled: false
|
|
305
|
-
});
|
|
306
|
-
receiver.client.on("connected", () => {
|
|
307
|
-
this.connected = true;
|
|
308
|
-
});
|
|
309
|
-
receiver.client.on("disconnected", () => {
|
|
310
|
-
this.connected = false;
|
|
311
|
-
});
|
|
312
|
-
const app = new App({
|
|
313
|
-
token: botToken,
|
|
314
|
-
receiver,
|
|
315
|
-
logLevel: LogLevel.ERROR
|
|
316
|
-
});
|
|
317
|
-
let authResult;
|
|
318
|
-
try {
|
|
319
|
-
authResult = await app.client.auth.test({ token: botToken });
|
|
320
|
-
} catch (error) {
|
|
321
|
-
this.recordConnection("auth-failed", errorMessageOf(error));
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
const processor = new FunnelSlackEventProcessor({
|
|
325
|
-
ownBotUserId: authResult.user_id ?? "",
|
|
326
|
-
ownBotId: authResult.bot_id ?? "",
|
|
327
|
-
minify: this.config.minify
|
|
328
|
-
});
|
|
329
|
-
const preprocess = this.preprocessEvent;
|
|
330
|
-
app.use(async (args) => {
|
|
331
|
-
const parsed = middlewareArgsSchema.safeParse(args);
|
|
332
|
-
if (!parsed.success || !parsed.data.event) {
|
|
333
|
-
await args.next();
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const rawEvent = parsed.data.event;
|
|
337
|
-
const eventId = crypto.randomUUID();
|
|
338
|
-
this.recordRaw(eventId, rawEvent);
|
|
339
|
-
const event = preprocess ? preprocess(rawEvent) : rawEvent;
|
|
340
|
-
if (event === null) {
|
|
341
|
-
this.recordProcessed(eventId, rawEvent, "skip:preprocess", "");
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const result = processor.process(event);
|
|
345
|
-
if (result.skip) {
|
|
346
|
-
this.recordProcessed(eventId, event, result.reason, "");
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
try {
|
|
350
|
-
await notify(result.content, result.meta);
|
|
351
|
-
} catch (error) {
|
|
352
|
-
this.recordProcessed(eventId, event, "emitted:delivery-failed", result.content);
|
|
353
|
-
throw error;
|
|
354
|
-
}
|
|
355
|
-
this.recordProcessed(eventId, event, "emitted", result.content);
|
|
356
|
-
if (result.shouldReact) try {
|
|
357
|
-
await app.client.reactions.add({
|
|
358
|
-
token: botToken,
|
|
359
|
-
channel: result.channel,
|
|
360
|
-
timestamp: result.timestamp,
|
|
361
|
-
name: "eyes"
|
|
362
|
-
});
|
|
363
|
-
} catch {}
|
|
364
|
-
});
|
|
365
|
-
app.error(async (error) => {
|
|
366
|
-
const message = errorMessageOf(error);
|
|
367
|
-
this.recordConnection("error", message);
|
|
368
|
-
this.logger?.error("Slack error", { error: message });
|
|
369
|
-
});
|
|
370
|
-
if (this.onAppCreated) await this.onAppCreated(app);
|
|
371
|
-
try {
|
|
372
|
-
await app.start();
|
|
373
|
-
} catch (error) {
|
|
374
|
-
this.recordConnection("error", errorMessageOf(error));
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
this.app = app;
|
|
378
|
-
this.connected = true;
|
|
379
|
-
this.recordConnection("connected", "");
|
|
380
|
-
}
|
|
381
|
-
async stop() {
|
|
382
|
-
if (!this.app) return;
|
|
383
|
-
try {
|
|
384
|
-
await this.app.stop();
|
|
385
|
-
this.recordConnection("disconnected", "");
|
|
386
|
-
} catch (error) {
|
|
387
|
-
this.recordConnection("error", errorMessageOf(error));
|
|
388
|
-
this.logger?.error("Slack stop error", { error: errorMessageOf(error) });
|
|
389
|
-
} finally {
|
|
390
|
-
this.app = null;
|
|
391
|
-
this.connected = false;
|
|
392
|
-
this.recordConnection("stopped", "");
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
isAlive() {
|
|
396
|
-
return this.app !== null && this.connected;
|
|
397
|
-
}
|
|
398
|
-
recordRaw(eventId, event) {
|
|
399
|
-
this.diagnosticLog?.recordRaw({
|
|
400
|
-
eventId,
|
|
401
|
-
type: "slack",
|
|
402
|
-
connectorId: this.config.id,
|
|
403
|
-
channelId: this.channelId,
|
|
404
|
-
payload: JSON.stringify(event)
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
recordProcessed(eventId, event, outcome, content) {
|
|
408
|
-
this.diagnosticLog?.recordProcessed({
|
|
409
|
-
eventId,
|
|
410
|
-
type: "slack",
|
|
411
|
-
connectorId: this.config.id,
|
|
412
|
-
channelId: this.channelId,
|
|
413
|
-
outcome,
|
|
414
|
-
payload: content || JSON.stringify(event)
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
recordConnection(status, detail) {
|
|
418
|
-
this.diagnosticLog?.recordConnection({
|
|
419
|
-
type: "slack",
|
|
420
|
-
connectorId: this.config.id,
|
|
421
|
-
channelId: this.channelId,
|
|
422
|
-
status,
|
|
423
|
-
detail
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
//#endregion
|
|
428
|
-
//#region lib/engine/connectors/slack-connector.ts
|
|
429
|
-
/**
|
|
430
|
-
* Slack connector descriptor. Pass `slackConnector()` to
|
|
431
|
-
* `new Funnel({ connectors: [...] })` to enable the type. Host launch hooks are
|
|
432
|
-
* closed over here, so they need no Funnel-level option plumbing.
|
|
433
|
-
*/
|
|
434
|
-
const slackConnector = (options = {}) => ({
|
|
435
|
-
type: "slack",
|
|
436
|
-
toolExposed: true,
|
|
437
|
-
createListener(config, deps) {
|
|
438
|
-
return new FunnelSlackListener({
|
|
439
|
-
config: slackConnectorSchema.parse(config),
|
|
440
|
-
channelId: deps.channelId,
|
|
441
|
-
logger: deps.logger,
|
|
442
|
-
diagnosticLog: deps.diagnosticLog,
|
|
443
|
-
onAppCreated: options.onAppCreated,
|
|
444
|
-
preprocessEvent: options.preprocessEvent
|
|
445
|
-
});
|
|
446
|
-
},
|
|
447
|
-
createAdapter(config) {
|
|
448
|
-
return new FunnelSlackAdapter({ config: slackConnectorSchema.parse(config) });
|
|
449
|
-
},
|
|
450
|
-
secretTokens(config) {
|
|
451
|
-
const parsed = slackConnectorSchema.parse(config);
|
|
452
|
-
return [parsed.botToken, parsed.appToken].filter((token) => token !== void 0);
|
|
453
|
-
},
|
|
454
|
-
buildConfig(input, context) {
|
|
455
|
-
return slackConnectorSchema.parse({
|
|
456
|
-
id: context.id,
|
|
457
|
-
type: "slack",
|
|
458
|
-
name: input.name,
|
|
459
|
-
...typeof input.botToken === "string" ? { botToken: input.botToken } : {},
|
|
460
|
-
...typeof input.appToken === "string" ? { appToken: input.appToken } : {},
|
|
461
|
-
...typeof input.botTokenEnv === "string" ? { botTokenEnv: input.botTokenEnv } : {},
|
|
462
|
-
...typeof input.appTokenEnv === "string" ? { appTokenEnv: input.appTokenEnv } : {},
|
|
463
|
-
minify: typeof input.minify === "boolean" ? input.minify : true,
|
|
464
|
-
createdAt: context.now,
|
|
465
|
-
updatedAt: context.now
|
|
466
|
-
});
|
|
467
|
-
},
|
|
468
|
-
applyUpdate(config, fields, context) {
|
|
469
|
-
const current = slackConnectorSchema.parse(config);
|
|
470
|
-
return slackConnectorSchema.parse({
|
|
471
|
-
id: current.id,
|
|
472
|
-
name: current.name,
|
|
473
|
-
type: "slack",
|
|
474
|
-
minify: current.minify,
|
|
475
|
-
createdAt: current.createdAt,
|
|
476
|
-
updatedAt: context.now,
|
|
477
|
-
...slotFields("botToken", "botTokenEnv", fields, current),
|
|
478
|
-
...slotFields("appToken", "appTokenEnv", fields, current)
|
|
479
|
-
});
|
|
480
|
-
},
|
|
481
|
-
operations: {}
|
|
482
|
-
});
|
|
483
|
-
//#endregion
|
|
484
|
-
export { FunnelSlackAdapter as i, FunnelSlackListener as n, FunnelSlackEventProcessor as r, slackConnector as t };
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region lib/engine/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 `process.env` 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
|
-
//#region lib/engine/connectors/slot-fields.ts
|
|
23
|
-
/**
|
|
24
|
-
* Resolves one token slot (e.g. botToken/botTokenEnv) for a connector update.
|
|
25
|
-
* The literal and the env-ref form are mutually exclusive: if `fields` supplies
|
|
26
|
-
* either, that form wins and the other key is omitted entirely; if it supplies
|
|
27
|
-
* neither, the connector's current slot is carried over unchanged. Returns a
|
|
28
|
-
* partial object spread into the rebuilt connector, so an omitted key is truly
|
|
29
|
-
* absent rather than set to undefined — switching a slot from literal to ref
|
|
30
|
-
* drops the stale literal instead of leaving both behind.
|
|
31
|
-
*/
|
|
32
|
-
const slotFields = (literalKey, envKey, fields, current) => {
|
|
33
|
-
const literal = fields[literalKey];
|
|
34
|
-
if (typeof literal === "string") return { [literalKey]: literal };
|
|
35
|
-
const envVar = fields[envKey];
|
|
36
|
-
if (typeof envVar === "string") return { [envKey]: envVar };
|
|
37
|
-
const result = {};
|
|
38
|
-
const currentLiteral = current[literalKey];
|
|
39
|
-
const currentEnv = current[envKey];
|
|
40
|
-
if (typeof currentLiteral === "string") result[literalKey] = currentLiteral;
|
|
41
|
-
if (typeof currentEnv === "string") result[envKey] = currentEnv;
|
|
42
|
-
return result;
|
|
43
|
-
};
|
|
44
|
-
//#endregion
|
|
45
|
-
export { resolveConnectorToken as n, slotFields as t };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|