@interactive-inc/claude-funnel 0.60.1 → 0.64.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-CRGb6B5_.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-BFIhyTfa.d.ts} +49 -10
- 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 +106 -132
- 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-tVcgckH6.d.ts} +6 -6
- package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
- package/dist/flume-source-listener-BNyAII7N.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-Ds6sHhA-.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-BoV8Hf-n.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-CxpWagbT.js +388 -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
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { t as NodeFunnelHttpClient } from "./node-http-client-u00atiKx.js";
|
|
2
|
+
import { t as errorMessageOf } from "./error-message-of-ColuYmAk.js";
|
|
3
|
+
import { t as discordConnectorSchema } from "./discord-connector-schema-B4YpWpR3.js";
|
|
4
|
+
import { t as FunnelConnectorAdapter } from "./connector-adapter-Dvs8N7ew.js";
|
|
5
|
+
import { t as resolveConnectorToken } from "./resolve-connector-token-DxDG9mhf.js";
|
|
6
|
+
import { i as resolveFlumeDeps, n as FunnelFlumeSourceListener, r as flumeLogHandler, t as slotFields } from "./slot-fields-D-pvMgTK.js";
|
|
7
|
+
import { FlumeDiscordGatewayIntents, FlumeDiscordSource } from "@interactive-inc/flume/discord";
|
|
8
|
+
//#region lib/engine/connectors/discord-adapter.ts
|
|
9
|
+
const DISCORD_API_BASE = "https://discord.com/api/v10";
|
|
10
|
+
const defaultHttp = new NodeFunnelHttpClient();
|
|
11
|
+
var FunnelDiscordAdapter = class extends FunnelConnectorAdapter {
|
|
12
|
+
token;
|
|
13
|
+
http;
|
|
14
|
+
constructor(deps) {
|
|
15
|
+
super();
|
|
16
|
+
this.token = resolveConnectorToken({
|
|
17
|
+
literal: deps.config.botToken,
|
|
18
|
+
envVar: deps.config.botTokenEnv,
|
|
19
|
+
env: deps.env ?? process.env,
|
|
20
|
+
label: `${deps.config.name}.botToken`
|
|
21
|
+
});
|
|
22
|
+
this.http = deps.http ?? defaultHttp;
|
|
23
|
+
Object.freeze(this);
|
|
24
|
+
}
|
|
25
|
+
async call(input) {
|
|
26
|
+
const method = (input.method || "GET").toUpperCase();
|
|
27
|
+
const path = input.path.startsWith("/") ? input.path : `/${input.path}`;
|
|
28
|
+
const body = input.body;
|
|
29
|
+
const hasBody = body !== null && typeof body === "object" && method !== "GET" && Object.keys(body).length > 0;
|
|
30
|
+
const res = await this.http.fetch({
|
|
31
|
+
method,
|
|
32
|
+
url: `${DISCORD_API_BASE}${path}`,
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bot ${this.token}`,
|
|
35
|
+
"Content-Type": "application/json"
|
|
36
|
+
},
|
|
37
|
+
body: hasBody ? JSON.stringify(input.body) : void 0
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) throw new Error(`Discord API failed (${res.status}): ${await res.text()}`);
|
|
40
|
+
if (res.status === 204) return null;
|
|
41
|
+
return await res.json();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region lib/engine/connectors/discord-event-processor.ts
|
|
46
|
+
var FunnelDiscordEventProcessor = class {
|
|
47
|
+
ownUserId;
|
|
48
|
+
constructor(props) {
|
|
49
|
+
this.ownUserId = props.ownUserId;
|
|
50
|
+
}
|
|
51
|
+
process(message) {
|
|
52
|
+
if (message.authorIsBot) return { skip: true };
|
|
53
|
+
const mentioned = this.ownUserId ? message.mentionedUserIds.includes(this.ownUserId) : false;
|
|
54
|
+
return {
|
|
55
|
+
skip: false,
|
|
56
|
+
content: JSON.stringify(message.raw),
|
|
57
|
+
meta: {
|
|
58
|
+
event_type: "discord",
|
|
59
|
+
channel_id: message.channelId,
|
|
60
|
+
user_id: message.authorId,
|
|
61
|
+
mentioned: String(mentioned),
|
|
62
|
+
guild_id: message.guildId ?? ""
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region lib/engine/connectors/discord-flume-listener.ts
|
|
69
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
+
const readString = (record, key) => {
|
|
71
|
+
const value = record[key];
|
|
72
|
+
return typeof value === "string" ? value : void 0;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Discord listener backed by `@interactive-inc/flume`'s `FlumeDiscordSource`
|
|
76
|
+
* (raw Gateway WebSocket + Zod). The processor layer
|
|
77
|
+
* (`FunnelDiscordEventProcessor`) is the application layer — self-skip,
|
|
78
|
+
* mention detection, meta shaping. Self-detection requires the bot's own
|
|
79
|
+
* user id, which Discord does not surface until READY; we read it from the
|
|
80
|
+
* READY payload on the first dispatch and build the processor then. Events
|
|
81
|
+
* seen before READY are impossible by protocol, so no event is lost.
|
|
82
|
+
*/
|
|
83
|
+
var FunnelFlumeDiscordListener = class extends FunnelFlumeSourceListener {
|
|
84
|
+
config;
|
|
85
|
+
env;
|
|
86
|
+
flumeDeps;
|
|
87
|
+
signal;
|
|
88
|
+
processor = null;
|
|
89
|
+
constructor(deps) {
|
|
90
|
+
super({
|
|
91
|
+
type: "discord",
|
|
92
|
+
connectorId: deps.config.id,
|
|
93
|
+
channelId: deps.channelId ?? null,
|
|
94
|
+
logger: deps.logger,
|
|
95
|
+
diagnosticLog: deps.diagnosticLog
|
|
96
|
+
});
|
|
97
|
+
this.config = deps.config;
|
|
98
|
+
this.env = deps.env ?? process.env;
|
|
99
|
+
this.flumeDeps = deps.flumeDeps ?? {};
|
|
100
|
+
this.signal = deps.signal;
|
|
101
|
+
}
|
|
102
|
+
async start(notify) {
|
|
103
|
+
this.diagnostics.recordConnection("started", "");
|
|
104
|
+
let token;
|
|
105
|
+
try {
|
|
106
|
+
token = resolveConnectorToken({
|
|
107
|
+
literal: this.config.botToken,
|
|
108
|
+
envVar: this.config.botTokenEnv,
|
|
109
|
+
env: this.env,
|
|
110
|
+
label: `${this.config.name}.botToken`
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
this.diagnostics.recordConnection("auth-failed", errorMessageOf(error));
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
const source = new FlumeDiscordSource({
|
|
117
|
+
token,
|
|
118
|
+
intents: FlumeDiscordGatewayIntents.Guilds | FlumeDiscordGatewayIntents.GuildMessages | FlumeDiscordGatewayIntents.MessageContent | FlumeDiscordGatewayIntents.DirectMessages
|
|
119
|
+
});
|
|
120
|
+
await this.runStart({
|
|
121
|
+
source,
|
|
122
|
+
onLog: flumeLogHandler(this.logger),
|
|
123
|
+
deps: resolveFlumeDeps(this.flumeDeps),
|
|
124
|
+
signal: this.signal,
|
|
125
|
+
onEvent: (event) => {
|
|
126
|
+
if (event.source !== "discord") return Promise.resolve();
|
|
127
|
+
return this.handleEvent(event, notify);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
onStop() {
|
|
132
|
+
this.processor = null;
|
|
133
|
+
}
|
|
134
|
+
async handleEvent(event, notify) {
|
|
135
|
+
if (event.type === "READY") {
|
|
136
|
+
this.adoptReady(event.data);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!this.processor) {
|
|
140
|
+
const skipId = crypto.randomUUID();
|
|
141
|
+
this.diagnostics.recordRaw(skipId, JSON.stringify(event.data));
|
|
142
|
+
this.diagnostics.recordProcessed(skipId, "skip:pre-ready", "");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const data = event.data;
|
|
146
|
+
const author = isRecord(data.author) ? data.author : null;
|
|
147
|
+
const authorIsBot = author !== null && author.bot === true;
|
|
148
|
+
const authorId = event.meta.user_id ?? "";
|
|
149
|
+
const channelId = event.meta.channel_id ?? "";
|
|
150
|
+
const guildId = event.meta.guild_id ?? null;
|
|
151
|
+
const mentions = Array.isArray(data.mentions) ? data.mentions.map((m) => isRecord(m) ? readString(m, "id") ?? "" : "").filter((id) => id !== "") : [];
|
|
152
|
+
const eventId = crypto.randomUUID();
|
|
153
|
+
const rawJson = JSON.stringify(data);
|
|
154
|
+
this.diagnostics.recordRaw(eventId, rawJson);
|
|
155
|
+
const result = this.processor.process({
|
|
156
|
+
authorId,
|
|
157
|
+
authorIsBot,
|
|
158
|
+
channelId,
|
|
159
|
+
guildId,
|
|
160
|
+
mentionedUserIds: mentions,
|
|
161
|
+
raw: data
|
|
162
|
+
});
|
|
163
|
+
if (result.skip) {
|
|
164
|
+
this.diagnostics.recordProcessed(eventId, "skip:bot", rawJson);
|
|
165
|
+
this.logger?.info("discord skip", { reason: "bot author" });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
await this.deliver(notify, eventId, rawJson, result.content, result.meta);
|
|
169
|
+
}
|
|
170
|
+
adoptReady(data) {
|
|
171
|
+
const fromUser = isRecord(data.user) ? readString(data.user, "id") : void 0;
|
|
172
|
+
const fromTop = readString(data, "user_id");
|
|
173
|
+
const ownUserId = fromUser ?? fromTop ?? "";
|
|
174
|
+
if (!ownUserId) {
|
|
175
|
+
const skipId = crypto.randomUUID();
|
|
176
|
+
this.diagnostics.recordRaw(skipId, JSON.stringify(data));
|
|
177
|
+
this.diagnostics.recordProcessed(skipId, "skip:ready-missing-user-id", "");
|
|
178
|
+
this.diagnostics.recordConnection("error", "discord READY payload had neither user.id nor user_id; processor not initialized");
|
|
179
|
+
this.logger?.error("discord READY missing user id", { connector: this.config.name });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
this.processor = new FunnelDiscordEventProcessor({ ownUserId });
|
|
183
|
+
}
|
|
184
|
+
async deliver(notify, eventId, rawJson, content, meta) {
|
|
185
|
+
try {
|
|
186
|
+
await notify(content, meta);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.diagnostics.recordProcessed(eventId, "emitted:delivery-failed", content || rawJson);
|
|
189
|
+
this.logger?.error("discord notify error", { error: errorMessageOf(error) });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.diagnostics.recordProcessed(eventId, "emitted", content);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region lib/engine/connectors/discord-connector.ts
|
|
197
|
+
/**
|
|
198
|
+
* Discord connector descriptor. Pass `discordConnector()` to
|
|
199
|
+
* `new Funnel({ connectors: [...] })` to enable the type.
|
|
200
|
+
*
|
|
201
|
+
* The listener is backed by `@interactive-inc/flume`'s `FlumeDiscordSource`
|
|
202
|
+
* (raw Gateway WebSocket).
|
|
203
|
+
*/
|
|
204
|
+
const discordConnector = () => ({
|
|
205
|
+
type: "discord",
|
|
206
|
+
toolExposed: true,
|
|
207
|
+
createListener(config, deps) {
|
|
208
|
+
return new FunnelFlumeDiscordListener({
|
|
209
|
+
config: discordConnectorSchema.parse(config),
|
|
210
|
+
channelId: deps.channelId,
|
|
211
|
+
logger: deps.logger,
|
|
212
|
+
diagnosticLog: deps.diagnosticLog,
|
|
213
|
+
signal: deps.signal
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
createAdapter(config, deps) {
|
|
217
|
+
return new FunnelDiscordAdapter({
|
|
218
|
+
config: discordConnectorSchema.parse(config),
|
|
219
|
+
http: deps.http
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
secretTokens(config) {
|
|
223
|
+
return [discordConnectorSchema.parse(config).botToken].filter((token) => token !== void 0);
|
|
224
|
+
},
|
|
225
|
+
buildConfig(input, context) {
|
|
226
|
+
return discordConnectorSchema.parse({
|
|
227
|
+
id: context.id,
|
|
228
|
+
type: "discord",
|
|
229
|
+
name: input.name,
|
|
230
|
+
...typeof input.botToken === "string" ? { botToken: input.botToken } : {},
|
|
231
|
+
...typeof input.botTokenEnv === "string" ? { botTokenEnv: input.botTokenEnv } : {},
|
|
232
|
+
createdAt: context.now,
|
|
233
|
+
updatedAt: context.now
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
applyUpdate(config, fields, context) {
|
|
237
|
+
const current = discordConnectorSchema.parse(config);
|
|
238
|
+
return discordConnectorSchema.parse({
|
|
239
|
+
id: current.id,
|
|
240
|
+
name: current.name,
|
|
241
|
+
type: "discord",
|
|
242
|
+
createdAt: current.createdAt,
|
|
243
|
+
updatedAt: context.now,
|
|
244
|
+
...slotFields("botToken", "botTokenEnv", fields, current)
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
operations: {}
|
|
248
|
+
});
|
|
249
|
+
//#endregion
|
|
250
|
+
export { FunnelDiscordAdapter as i, FunnelFlumeDiscordListener as n, FunnelDiscordEventProcessor as r, discordConnector as t };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region lib/engine/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 `process.env` at listener start. The
|
|
7
|
+
* reference form keeps the secret out of settings.json, but is only set through
|
|
8
|
+
* the engine API (`new Funnel(...)`); funnel.json and the `fnl` CLI produce
|
|
9
|
+
* literals.
|
|
10
|
+
*/
|
|
11
|
+
declare const discordConnectorSchema: z.ZodObject<{
|
|
12
|
+
id: z.ZodString;
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
type: z.ZodLiteral<"discord">;
|
|
15
|
+
botToken: z.ZodOptional<z.ZodString>;
|
|
16
|
+
botTokenEnv: z.ZodOptional<z.ZodString>;
|
|
17
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
18
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { discordConnectorSchema as n, DiscordConnectorConfig as t };
|
package/dist/docs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelDocs } from "./funnel-docs-
|
|
1
|
+
import { t as FunnelDocs } from "./funnel-docs-C-ge0MuB.js";
|
|
2
2
|
export { FunnelDocs };
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as DoctorReport, r as FunnelDoctor, t as DoctorFixMode } from "./funnel-doctor-
|
|
1
|
+
import { n as DoctorReport, r as FunnelDoctor, t as DoctorFixMode } from "./funnel-doctor-CnRQi4kM.js";
|
|
2
2
|
export { DoctorFixMode, DoctorReport, FunnelDoctor };
|
package/dist/doctor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as FunnelDoctor } from "./funnel-doctor-
|
|
1
|
+
import { t as FunnelDoctor } from "./funnel-doctor-XrI2GBH8.js";
|
|
2
2
|
export { FunnelDoctor };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as ChannelConfig } from "./settings-schema-
|
|
2
|
-
import { n as FunnelIdGenerator } from "./settings-reader-
|
|
3
|
-
import { B as
|
|
4
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
1
|
+
import { t as ChannelConfig } from "./settings-schema-BL_c2Udm.js";
|
|
2
|
+
import { n as FunnelIdGenerator } from "./settings-reader-BNxjsxCB.js";
|
|
3
|
+
import { B as FunnelProcessRunner, W as FunnelLogger } from "./connector-descriptor-BFIhyTfa.js";
|
|
4
|
+
import { n as FunnelFileSystem } from "./file-system-VhwwXZbm.js";
|
|
5
5
|
|
|
6
6
|
//#region lib/engine/claude/channel-resolver.d.ts
|
|
7
7
|
type ChannelResolver = {
|
|
@@ -136,7 +136,7 @@ type Deps = {
|
|
|
136
136
|
process?: FunnelProcessRunner;
|
|
137
137
|
dir?: string;
|
|
138
138
|
};
|
|
139
|
-
declare class
|
|
139
|
+
declare class FunnelFileProcessGuard implements ProcessGuard {
|
|
140
140
|
private readonly fs;
|
|
141
141
|
private readonly process;
|
|
142
142
|
private readonly pidDir;
|
|
@@ -148,4 +148,4 @@ declare class FileProcessGuard implements ProcessGuard {
|
|
|
148
148
|
private readRecord;
|
|
149
149
|
}
|
|
150
150
|
//#endregion
|
|
151
|
-
export { ProcessGuard as a, ChannelResolver as c, SessionStore as i, FunnelClaude as n, McpInstaller as o, LaunchOptions as r, GatewayController as s,
|
|
151
|
+
export { ProcessGuard as a, ChannelResolver as c, SessionStore as i, FunnelClaude as n, McpInstaller as o, LaunchOptions as r, GatewayController as s, FunnelFileProcessGuard as t };
|
|
@@ -21,6 +21,14 @@ declare abstract class FunnelFileSystem {
|
|
|
21
21
|
}): void;
|
|
22
22
|
abstract readdirSync(path: string): string[];
|
|
23
23
|
abstract statSync(path: string): FileStat;
|
|
24
|
+
/**
|
|
25
|
+
* Run `fn` while holding an exclusive lock on `lockPath`. The lock file is
|
|
26
|
+
* created atomically (`O_EXCL`) so two processes cannot both enter. A stale
|
|
27
|
+
* lock whose owning pid is no longer alive is forcibly broken (this is what
|
|
28
|
+
* keeps a SIGKILL'd CLI command from wedging the lock forever). The Memory
|
|
29
|
+
* impl is a no-op because tests are single-threaded.
|
|
30
|
+
*/
|
|
31
|
+
abstract withFileLock<T>(lockPath: string, fn: () => T): T;
|
|
24
32
|
}
|
|
25
33
|
//#endregion
|
|
26
34
|
export { FunnelFileSystem as n, FileStat as t };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { W as FunnelLogger, _ as ConnectorDiagnosticLog, g as ConnectorConnectionStatus, j as FunnelConnectorListener } from "./connector-descriptor-BFIhyTfa.js";
|
|
2
|
+
import { FlumeEventHandler, FlumeLogHandler, FlumeReconnectOptions, FlumeRunning, FlumeRuntimeDeps, FlumeSource, FlumeStatus } from "@interactive-inc/flume";
|
|
3
|
+
|
|
4
|
+
//#region lib/engine/connectors/connector-diagnostics-recorder.d.ts
|
|
5
|
+
type Props$1 = {
|
|
6
|
+
type: string;
|
|
7
|
+
connectorId: string | null;
|
|
8
|
+
channelId: string | null;
|
|
9
|
+
log: ConnectorDiagnosticLog | undefined;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a `ConnectorDiagnosticLog` with the per-listener axes (`type` /
|
|
13
|
+
* `connectorId` / `channelId`) so call sites only pass the row-specific
|
|
14
|
+
* fields. When no log is wired every call is a silent no-op.
|
|
15
|
+
*/
|
|
16
|
+
declare class FunnelConnectorDiagnosticsRecorder {
|
|
17
|
+
private readonly props;
|
|
18
|
+
constructor(props: Props$1);
|
|
19
|
+
recordRaw(eventId: string, payload: string): void;
|
|
20
|
+
recordProcessed(eventId: string, outcome: string, payload: string): void;
|
|
21
|
+
recordConnection(status: ConnectorConnectionStatus, detail: string): void;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region lib/engine/connectors/flume-source-listener.d.ts
|
|
25
|
+
type Props = {
|
|
26
|
+
/** Funnel connector type ("slack" / "discord" / "gh") — stamped onto diagnostic rows. */type: string;
|
|
27
|
+
connectorId: string;
|
|
28
|
+
channelId: string | null;
|
|
29
|
+
logger?: FunnelLogger;
|
|
30
|
+
diagnosticLog?: ConnectorDiagnosticLog;
|
|
31
|
+
};
|
|
32
|
+
type RunStartOptions = {
|
|
33
|
+
source: FlumeSource; /** Typed event handler — receives the source's events only (logs are routed separately). */
|
|
34
|
+
onEvent: FlumeEventHandler; /** Optional log handler for everything the firehose emits (including status transitions). */
|
|
35
|
+
onLog?: FlumeLogHandler;
|
|
36
|
+
deps?: FlumeRuntimeDeps;
|
|
37
|
+
/**
|
|
38
|
+
* Optional AbortSignal forwarded to the underlying Flume. When aborted, the
|
|
39
|
+
* Flume auto-closes every source and resolves to `FlumeClosed`. Use this to
|
|
40
|
+
* propagate a host-level shutdown (SIGTERM, supervisor stop, parent timeout)
|
|
41
|
+
* down to the WebSocket layer without racing through `stop()`.
|
|
42
|
+
*/
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
/**
|
|
45
|
+
* Reconnect policy override forwarded to the Flume. The base class enables
|
|
46
|
+
* reconnect with Flume's defaults (infinite attempts, 1s base / 30s max
|
|
47
|
+
* exponential backoff with jitter) so a wifi drop or upstream socket close
|
|
48
|
+
* is auto-recovered. Subclasses can pass a stricter `{ maxAttempts, ... }`
|
|
49
|
+
* or `false` to opt out of reconnect entirely. Defaults to `true`.
|
|
50
|
+
*/
|
|
51
|
+
reconnect?: boolean | FlumeReconnectOptions;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Status event reconstructed from the firehose `status` log entry. Funnel
|
|
55
|
+
* keeps its own shape because Flume 0.9 collapsed the dedicated `onStatus`
|
|
56
|
+
* callback into the unified `onEvent` firehose — state transitions now arrive
|
|
57
|
+
* as `log.action === "status"` entries with `detail: { from, to, reason }`.
|
|
58
|
+
*/
|
|
59
|
+
type StatusEvent = {
|
|
60
|
+
source: string;
|
|
61
|
+
status: FlumeStatus;
|
|
62
|
+
detail: string | null;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Shared lifecycle for any listener whose transport is a `FlumeSource`. Owns
|
|
66
|
+
* the per-listener `Flume` instance + the `FlumeRunning` handle returned by
|
|
67
|
+
* `open()`, the connected/alive bit, the `FlumeStatus ↔
|
|
68
|
+
* ConnectorConnectionStatus` mapping, and the close sequence — every Flume
|
|
69
|
+
* subclass plugs in only its own token resolution, source construction, and
|
|
70
|
+
* event dispatch around this skeleton.
|
|
71
|
+
*
|
|
72
|
+
* Flume 0.9 collapsed every observation channel into one firehose: events
|
|
73
|
+
* and all logs arrive through `onEvent` as a discriminated union
|
|
74
|
+
* (`{ kind: "event" } | { kind: "log" }`). This base class splits that back
|
|
75
|
+
* into the funnel-shaped trio (typed event handler, log forward, status
|
|
76
|
+
* mapping) so subclasses keep their per-protocol code unchanged.
|
|
77
|
+
*/
|
|
78
|
+
declare abstract class FunnelFlumeSourceListener extends FunnelConnectorListener {
|
|
79
|
+
protected readonly logger: FunnelLogger | undefined;
|
|
80
|
+
protected readonly diagnostics: FunnelConnectorDiagnosticsRecorder;
|
|
81
|
+
protected readonly type: string;
|
|
82
|
+
protected running: FlumeRunning | null;
|
|
83
|
+
protected connected: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Flipped on by Flume's `reconnecting` status, off when the new socket
|
|
86
|
+
* lands on `connected` or the source gives up with `disconnected`. Used by
|
|
87
|
+
* `isAlive()` to treat a brief reconnect window as "still alive" so the
|
|
88
|
+
* supervisor does not preempt Flume's in-progress recovery with a heavier
|
|
89
|
+
* stop+start cycle (which would discard auth.test results and rebuild
|
|
90
|
+
* every per-listener state).
|
|
91
|
+
*/
|
|
92
|
+
protected reconnecting: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Promise chain that serializes typed-event delivery. Flume's emitItem
|
|
95
|
+
* fire-and-forgets the onEvent callback (see flume.ts emitItem:
|
|
96
|
+
* `Promise.resolve(onEvent(item)).catch(() => {})`), so awaiting onEvent
|
|
97
|
+
* inside the handler does NOT pause flume's source queue — multiple event
|
|
98
|
+
* deliveries would race their microtask chains. Chaining each new event
|
|
99
|
+
* onto the previous promise's `.then(...)` guarantees per-listener
|
|
100
|
+
* end-to-end FIFO regardless of whether the notify path is sync or async.
|
|
101
|
+
*/
|
|
102
|
+
private deliveryChain;
|
|
103
|
+
constructor(props: Props);
|
|
104
|
+
/**
|
|
105
|
+
* Assemble a single-source Flume, open it, and store the `FlumeRunning`
|
|
106
|
+
* handle. Records `error` on any `Error` returned by `flume.open()` and
|
|
107
|
+
* rethrows so the supervisor sees the failure.
|
|
108
|
+
*
|
|
109
|
+
* The firehose handler routes:
|
|
110
|
+
* - `kind: "event"` → subclass's typed `onEvent`
|
|
111
|
+
* - `kind: "log"` with `action === "status"` → `handleStatus()`
|
|
112
|
+
* - `kind: "log"` (any) → optional `onLog` handler
|
|
113
|
+
*/
|
|
114
|
+
protected runStart(options: RunStartOptions): Promise<void>;
|
|
115
|
+
stop(): Promise<void>;
|
|
116
|
+
isAlive(): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Maps Flume's transport status to the connection table. `reconnecting`
|
|
119
|
+
* deliberately produces no row — Flume drives many transient reconnects per
|
|
120
|
+
* minute on a flaky network, and the row would drown the more meaningful
|
|
121
|
+
* `connected`/`disconnected` pair. The `reconnecting` flag still flips so
|
|
122
|
+
* `isAlive()` can surface it to the supervisor.
|
|
123
|
+
*/
|
|
124
|
+
protected handleStatus(event: StatusEvent): void;
|
|
125
|
+
/**
|
|
126
|
+
* Hook for subclass-specific cleanup that has to run inside the stop()
|
|
127
|
+
* finally block (after the running handle is cleared, before the `stopped`
|
|
128
|
+
* row is recorded). Default is no-op.
|
|
129
|
+
*/
|
|
130
|
+
protected onStop(): void;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { FunnelFlumeSourceListener as t };
|