@interactive-inc/claude-funnel 0.52.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +25 -3
  2. package/dist/bin.js +1276 -520
  3. package/dist/claude.d.ts +22 -5
  4. package/dist/claude.js +456 -169
  5. package/dist/connector-adapter-1PxjN-Uk.d.ts +25 -0
  6. package/dist/{connector-adapter-D5Utumgz.js → connector-adapter-qwXLjQId.js} +1 -1
  7. package/dist/{connector-listener-DU54DN-f.js → connector-listener-CpHBecCj.js} +1 -1
  8. package/dist/connectors/discord.d.ts +6 -6
  9. package/dist/connectors/discord.js +2 -2
  10. package/dist/connectors/gh.d.ts +6 -6
  11. package/dist/connectors/gh.js +2 -2
  12. package/dist/connectors/schedule.d.ts +12 -2
  13. package/dist/connectors/schedule.js +2 -2
  14. package/dist/connectors/slack.d.ts +3 -3
  15. package/dist/connectors/slack.js +2 -2
  16. package/dist/{connector-diagnostic-log-yTOojKUR.d.ts → diagnostic-log-Bxe7Bbvw.d.ts} +2 -2
  17. package/dist/diagnostic-sql-reader-CzYgZpq2.js +83 -0
  18. package/dist/diagnostics.d.ts +2 -0
  19. package/dist/diagnostics.js +2 -0
  20. package/dist/{discord-connector-schema-CBDyGdOI.js → discord-connector-schema-B_N6IXLz.js} +1 -1
  21. package/dist/{discord-connector-schema-R0Uu-3ns.d.ts → discord-connector-schema-CPgcZkXh.d.ts} +1 -1
  22. package/dist/{discord-listener-_jSE3HsQ.js → discord-listener-C0MoKdQO.js} +6 -6
  23. package/dist/docs.d.ts +2 -0
  24. package/dist/docs.js +2 -0
  25. package/dist/doctor.d.ts +2 -0
  26. package/dist/doctor.js +2 -0
  27. package/dist/{file-process-guard-BgrVHe9I.d.ts → file-process-guard-DI1742H5.d.ts} +31 -15
  28. package/dist/funnel-diagnostics-BpKYrMSu.js +300 -0
  29. package/dist/funnel-diagnostics-qWy5tPSq.d.ts +176 -0
  30. package/dist/funnel-docs-dXPokzr5.d.ts +18 -0
  31. package/dist/funnel-docs-ng5K8w4j.js +653 -0
  32. package/dist/funnel-doctor-BF3Rdgk0.d.ts +34 -0
  33. package/dist/funnel-doctor-CApCezTq.js +82 -0
  34. package/dist/funnel-recovery-BUBsu7WX.d.ts +101 -0
  35. package/dist/funnel-recovery-D9CxD5Zs.js +134 -0
  36. package/dist/gateway/daemon.js +810 -211
  37. package/dist/{settings-store-D2XSXTyt.js → gateway-base-url-6foMXfFf.js} +19 -6
  38. package/dist/gateway.d.ts +3 -3
  39. package/dist/gateway.js +3 -2
  40. package/dist/{gh-connector-schema-eoTtHbY6.d.ts → gh-connector-schema-CU1ojfIF.d.ts} +1 -1
  41. package/dist/{gh-connector-schema-o3Q1-ojL.js → gh-connector-schema-DUcZgN2Q.js} +1 -1
  42. package/dist/{gh-listener-DH-fClQm.js → gh-listener-Dsx6AmhH.js} +5 -5
  43. package/dist/{index-NFs2jzCa.d.ts → index-CrngHrne.d.ts} +187 -619
  44. package/dist/index.d.ts +16 -11
  45. package/dist/index.js +512 -976
  46. package/dist/{local-config-json-schema-8IHjS4Q7.js → local-config-json-schema-DE1zkMcb.js} +35 -9
  47. package/dist/{local-config-sync-BdsrDZOu.d.ts → local-config-sync-B8b04LrZ.d.ts} +45 -25
  48. package/dist/local-config.d.ts +2 -2
  49. package/dist/local-config.js +2 -2
  50. package/dist/{memory-connector-diagnostic-log-CrW1ltLM.js → memory-diagnostic-log-BZ1VD80X.js} +61 -99
  51. package/dist/{memory-token-prompter-B5FFCsGP.d.ts → memory-token-prompter-Lo3YRDzq.d.ts} +4 -4
  52. package/dist/{memory-token-prompter-CLerGsgM.js → memory-token-prompter-vBXxY20-.js} +2 -2
  53. package/dist/{profiles-f0mNmEyP.d.ts → profiles-EHTeCOqB.d.ts} +3 -2
  54. package/dist/profiles.d.ts +1 -1
  55. package/dist/profiles.js +1 -1
  56. package/dist/recovery.d.ts +2 -0
  57. package/dist/recovery.js +2 -0
  58. package/dist/{resolve-connector-token-BHmZLRrV.js → resolve-connector-token-CczqG_Ig.js} +1 -1
  59. package/dist/{schedule-connector-schema-iCI61gzU.js → schedule-connector-schema-B_xO5z5B.js} +1 -1
  60. package/dist/{schedule-listener-CUyUFFR1.d.ts → schedule-listener-DKh0hnkK.d.ts} +5 -5
  61. package/dist/{schedule-listener-ePAjians.js → schedule-listener-DP9Jhc6U.js} +14 -4
  62. package/dist/settings-reader-CBrgz01o.d.ts +18 -0
  63. package/dist/{settings-reader-BSU6JyvM.d.ts → settings-schema-zhnMIa8I.d.ts} +1 -16
  64. package/dist/{slack-connector-schema-BCNWluHM.js → slack-connector-schema-C1zEf4TG.js} +1 -1
  65. package/dist/{slack-listener-Bv5xI9gC.d.ts → slack-listener-COQA8wAZ.d.ts} +4 -4
  66. package/dist/{slack-listener-ClQuHhEF.js → slack-listener-DUKPcpJH.js} +7 -7
  67. package/dist/{mcp-Dr-nIBwN.js → yaml-render-OhUN-qkS.js} +52 -34
  68. package/package.json +21 -1
  69. package/dist/connector-adapter-DKgsVuMH.d.ts +0 -11
  70. /package/dist/{file-system-BeOKXjlV.d.ts → file-system-Wub9Nto4.d.ts} +0 -0
  71. /package/dist/{process-runner-DfniuWVU.d.ts → process-runner-D5I_jhYQ.d.ts} +0 -0
  72. /package/dist/{profiles-wMRnjSid.js → profiles-MnXvYfZF.js} +0 -0
@@ -0,0 +1,300 @@
1
+ import { t as ConnectorDiagnosticSqlReader } from "./diagnostic-sql-reader-CzYgZpq2.js";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ //#region lib/services/diagnostics/diagnostic-event.ts
5
+ const stringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
6
+ const numberOrNull = (value) => typeof value === "number" ? value : null;
7
+ const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
8
+ const isStringKeyedObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
9
+ const parsePayloadObject = (payload) => {
10
+ if (payload === null) return null;
11
+ try {
12
+ const parsed = JSON.parse(payload);
13
+ if (isStringKeyedObject(parsed)) return parsed;
14
+ } catch {
15
+ return null;
16
+ }
17
+ return null;
18
+ };
19
+ const truncate = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
20
+ const previewOf = (payload) => {
21
+ if (typeof payload !== "string" || payload.length === 0) return null;
22
+ const parsed = parsePayloadObject(payload);
23
+ if (parsed !== null && "text" in parsed) return truncate(String(parsed.text), 60);
24
+ return truncate(payload, 60);
25
+ };
26
+ const toDiagnosticEvent = (row) => {
27
+ const payload = stringOrNull(row.payload);
28
+ return {
29
+ seq: numberOrNull(row.seq),
30
+ ts: numberOrNull(row.ts),
31
+ type: stringOr(row.type, "?"),
32
+ outcome: stringOr(row.outcome, "?"),
33
+ eventId: stringOrNull(row.event_id),
34
+ payload,
35
+ payloadParsed: parsePayloadObject(payload),
36
+ preview: previewOf(row.payload)
37
+ };
38
+ };
39
+ const toDiagnosticConnectionError = (row) => ({
40
+ seq: numberOrNull(row.seq),
41
+ ts: numberOrNull(row.ts),
42
+ type: stringOr(row.type, "?"),
43
+ status: stringOr(row.status, "?"),
44
+ detail: stringOrNull(row.detail)
45
+ });
46
+ const queryRows = (reader, sql, params) => {
47
+ try {
48
+ return reader.query(sql, params);
49
+ } finally {
50
+ reader.close();
51
+ }
52
+ };
53
+ //#endregion
54
+ //#region lib/services/diagnostics/funnel-diagnostics.ts
55
+ const isGatewayStatusResponse = (value) => {
56
+ if (value === null || typeof value !== "object") return false;
57
+ if (!("clients" in value) || !Array.isArray(value.clients)) return false;
58
+ if (!("listeners" in value) || !Array.isArray(value.listeners)) return false;
59
+ return true;
60
+ };
61
+ const connectorOf = (channel, connectorId) => {
62
+ if (connectorId === null) return void 0;
63
+ return channel.connectors?.find((connector) => connector.id === connectorId)?.name;
64
+ };
65
+ const buildDiagnosis = (report) => {
66
+ const rootCause = (report.connectionErrors[report.connectionErrors.length - 1] ?? null)?.detail ?? null;
67
+ if (!report.gateway.running) return {
68
+ status: "error",
69
+ message: "gateway is not running",
70
+ nextActions: ["fnl gateway start"],
71
+ rootCause: null
72
+ };
73
+ const channel = report.channel;
74
+ if (!(report.listeners.length > 0)) return {
75
+ status: "warn",
76
+ message: "no connectors configured on this channel",
77
+ nextActions: [`fnl channels ${channel} connectors add <name> --type=slack ...`],
78
+ rootCause: null
79
+ };
80
+ const allDead = report.listeners.every((l) => !l.alive);
81
+ const someDead = report.listeners.some((l) => !l.alive);
82
+ if (allDead) return {
83
+ status: "error",
84
+ message: "all listeners are dead",
85
+ nextActions: ["fnl doctor --fix", "fnl doctor --fix --aggressive"],
86
+ rootCause
87
+ };
88
+ if (someDead) return {
89
+ status: "warn",
90
+ message: "some listeners are dead",
91
+ nextActions: ["fnl doctor --fix"],
92
+ rootCause
93
+ };
94
+ if (report.claudeClients === 0) return {
95
+ status: "warn",
96
+ message: "no Claude connected to this channel",
97
+ nextActions: [`fnl claude --channel ${channel}`],
98
+ rootCause: null
99
+ };
100
+ if (report.listeners.some((l) => l.errors > 0)) return {
101
+ status: "warn",
102
+ message: "listeners have errors",
103
+ nextActions: ["fnl gateway logs"],
104
+ rootCause
105
+ };
106
+ return {
107
+ status: "ok",
108
+ message: "everything looks healthy",
109
+ nextActions: [],
110
+ rootCause: null
111
+ };
112
+ };
113
+ /**
114
+ * Programmable diagnostics surface — used by both the CLI (fnl debug …) and
115
+ * the MCP tools (fnl_debug, fnl_recent_events, …). Pure read-side, no
116
+ * mutation; pair with FunnelRecovery for self-healing actions.
117
+ */
118
+ var FunnelDiagnostics = class {
119
+ constructor(props) {
120
+ this.props = props;
121
+ Object.freeze(this);
122
+ }
123
+ async diagnose(channelName) {
124
+ const channels = this.props.channels.list();
125
+ const target = channelName ? channels.find((ch) => ch.name === channelName) ?? null : channels[0] ?? null;
126
+ if (!target) return null;
127
+ const gatewayBody = await this.fetchGatewayStatus();
128
+ const store = this.resolveStore();
129
+ return this.buildChannelDiagnosis(target, gatewayBody, store, 5);
130
+ }
131
+ async diagnoseAll() {
132
+ const channels = this.props.channels.list();
133
+ const gatewayBody = await this.fetchGatewayStatus();
134
+ const store = this.resolveStore();
135
+ const reports = await Promise.all(channels.map((ch) => this.buildChannelDiagnosis(ch, gatewayBody, store, 5)));
136
+ const errorChannels = reports.filter((r) => r.diagnosis.status === "error").map((r) => r.channel);
137
+ const warnChannels = reports.filter((r) => r.diagnosis.status === "warn").map((r) => r.channel);
138
+ const okChannels = reports.filter((r) => r.diagnosis.status === "ok").map((r) => r.channel);
139
+ const uniqueActions = [...new Set(reports.flatMap((r) => r.diagnosis.nextActions))];
140
+ return {
141
+ summary: {
142
+ total: reports.length,
143
+ ok: okChannels.length,
144
+ warn: warnChannels.length,
145
+ error: errorChannels.length,
146
+ criticalChannels: errorChannels,
147
+ warnChannels,
148
+ suggestedActions: uniqueActions
149
+ },
150
+ channels: reports
151
+ };
152
+ }
153
+ async recentEvents(channelName, limit = 20) {
154
+ const store = this.resolveStore();
155
+ if (!store) return [];
156
+ const channelId = this.resolveChannelId(channelName);
157
+ if (channelName && !channelId) return [];
158
+ const reader = new ConnectorDiagnosticSqlReader(store);
159
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload FROM processed ORDER BY seq DESC LIMIT ?", [limit]);
160
+ if (rows instanceof Error) return [];
161
+ return rows.reverse().map(toDiagnosticEvent);
162
+ }
163
+ async droppedEvents(channelName, limit = 20) {
164
+ const store = this.resolveStore();
165
+ if (!store) return [];
166
+ const channelId = this.resolveChannelId(channelName);
167
+ if (channelName && !channelId) return [];
168
+ const reader = new ConnectorDiagnosticSqlReader(store);
169
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE channel_id = ? AND outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, outcome, payload, event_id FROM processed WHERE outcome LIKE 'skip:%' ORDER BY seq DESC LIMIT ?", [limit]);
170
+ if (rows instanceof Error) return [];
171
+ return rows.reverse().map(toDiagnosticEvent);
172
+ }
173
+ async connectionErrors(channelName, limit = 20) {
174
+ const store = this.resolveStore();
175
+ if (!store) return [];
176
+ const channelId = this.resolveChannelId(channelName);
177
+ if (channelName && !channelId) return [];
178
+ const reader = new ConnectorDiagnosticSqlReader(store);
179
+ const rows = channelId ? queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [channelId, limit]) : queryRows(reader, "SELECT seq, ts, type, status, detail FROM connection WHERE status IN ('auth-failed','error') ORDER BY seq DESC LIMIT ?", [limit]);
180
+ if (rows instanceof Error) return [];
181
+ return rows.reverse().map(toDiagnosticConnectionError);
182
+ }
183
+ async replay(channelName, seq) {
184
+ const channel = this.props.channels.list().find((ch) => ch.name === channelName);
185
+ if (!channel) return { state: "not-found" };
186
+ const store = this.resolveStore();
187
+ if (!store) return {
188
+ state: "error",
189
+ reason: "no diagnostic store yet"
190
+ };
191
+ const reader = new ConnectorDiagnosticSqlReader(store);
192
+ const rows = seq !== void 0 ? queryRows(reader, "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND seq = ? LIMIT 1", [channel.id, seq]) : queryRows(reader, "SELECT seq, event_id, type, payload, connector_id, channel_id FROM processed WHERE channel_id = ? AND outcome LIKE 'emitted%' ORDER BY seq DESC LIMIT 1", [channel.id]);
193
+ if (rows instanceof Error) return {
194
+ state: "error",
195
+ reason: rows.message
196
+ };
197
+ const firstRow = rows[0];
198
+ if (!firstRow) return { state: "not-found" };
199
+ const replaySeq = typeof firstRow.seq === "number" ? firstRow.seq : null;
200
+ const eventId = typeof firstRow.event_id === "string" ? firstRow.event_id : null;
201
+ const connectorId = typeof firstRow.connector_id === "string" ? firstRow.connector_id : null;
202
+ let content = typeof firstRow.payload === "string" ? firstRow.payload : null;
203
+ if ((!content || content.length === 0) && eventId) {
204
+ const rawRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT payload FROM raw WHERE event_id = ? LIMIT 1", [eventId]);
205
+ const rawRow = rawRows instanceof Error ? null : rawRows[0];
206
+ if (rawRow) content = typeof rawRow.payload === "string" ? rawRow.payload : null;
207
+ }
208
+ if (!content) return {
209
+ state: "error",
210
+ reason: "event has no payload to replay"
211
+ };
212
+ const connectorName = connectorOf(channel, connectorId);
213
+ const result = await this.props.publisher.publish(channel.name, {
214
+ content,
215
+ connector: connectorName
216
+ });
217
+ if (result.state === "offline") return { state: "offline" };
218
+ if (result.state === "error") return {
219
+ state: "error",
220
+ reason: result.reason
221
+ };
222
+ return {
223
+ state: "ok",
224
+ seq: replaySeq,
225
+ offset: result.offset,
226
+ preview: content.slice(0, 60)
227
+ };
228
+ }
229
+ resolveStore() {
230
+ const tmpDir = this.props.tmpDir;
231
+ const rawPath = join(tmpDir, "connector-raw.db");
232
+ const processedPath = join(tmpDir, "connector-processed.db");
233
+ const connectionPath = join(tmpDir, "connector-connection.db");
234
+ if (!existsSync(rawPath) || !existsSync(processedPath) || !existsSync(connectionPath)) return null;
235
+ return {
236
+ rawPath,
237
+ processedPath,
238
+ connectionPath
239
+ };
240
+ }
241
+ resolveChannelId(channelName) {
242
+ if (!channelName) return null;
243
+ return this.props.channels.list().find((ch) => ch.name === channelName)?.id ?? null;
244
+ }
245
+ async fetchGatewayStatus() {
246
+ const gatewayStatus = this.props.gateway.getStatus();
247
+ if (!gatewayStatus.running) return null;
248
+ const token = this.props.gatewayToken.read();
249
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
250
+ const res = await fetch(`http://127.0.0.1:${gatewayStatus.port}/status`, { headers }).catch(() => null);
251
+ if (!res || !res.ok) return null;
252
+ const body = await res.json();
253
+ return isGatewayStatusResponse(body) ? body : null;
254
+ }
255
+ async buildChannelDiagnosis(target, gatewayBody, store, eventLimit) {
256
+ const gatewayStatus = this.props.gateway.getStatus();
257
+ const targetName = target.name;
258
+ const baseReport = {
259
+ channel: targetName,
260
+ channelId: target.id,
261
+ gateway: {
262
+ running: gatewayStatus.running,
263
+ pid: gatewayStatus.pid,
264
+ port: gatewayStatus.running ? gatewayStatus.port : null,
265
+ uptimeMs: gatewayBody?.uptimeMs ?? null
266
+ },
267
+ listeners: [],
268
+ claudeClients: 0,
269
+ recentEvents: [],
270
+ connectionErrors: []
271
+ };
272
+ if (gatewayBody) {
273
+ baseReport.listeners = gatewayBody.listeners.filter((l) => l.channelName === targetName).map((l) => ({
274
+ name: l.name,
275
+ type: l.type,
276
+ alive: l.alive,
277
+ events: l.events,
278
+ errors: l.errors,
279
+ lastEventAt: l.lastEventAt
280
+ }));
281
+ baseReport.claudeClients = gatewayBody.clients.filter((cl) => cl.channelName === targetName).length;
282
+ }
283
+ if (store) {
284
+ const evRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT seq, ts, type, outcome, payload FROM processed WHERE channel_id = ? ORDER BY seq DESC LIMIT ?", [target.id, eventLimit]);
285
+ if (!(evRows instanceof Error)) baseReport.recentEvents = evRows.reverse().map(toDiagnosticEvent);
286
+ const hasDeadListeners = baseReport.listeners.some((l) => !l.alive);
287
+ const hasListenerErrors = baseReport.listeners.some((l) => l.errors > 0);
288
+ if (hasDeadListeners || hasListenerErrors) {
289
+ const errRows = queryRows(new ConnectorDiagnosticSqlReader(store), "SELECT ts, type, status, detail FROM connection WHERE channel_id = ? AND status IN ('auth-failed','error') ORDER BY seq DESC LIMIT 3", [target.id]);
290
+ if (!(errRows instanceof Error)) baseReport.connectionErrors = errRows.reverse().map(toDiagnosticConnectionError);
291
+ }
292
+ }
293
+ return {
294
+ ...baseReport,
295
+ diagnosis: buildDiagnosis(baseReport)
296
+ };
297
+ }
298
+ };
299
+ //#endregion
300
+ export { toDiagnosticEvent as a, toDiagnosticConnectionError as i, previewOf as n, queryRows as r, FunnelDiagnostics as t };
@@ -0,0 +1,176 @@
1
+ import { t as ChannelConfig } from "./settings-schema-zhnMIa8I.js";
2
+
3
+ //#region lib/gateway/diagnostic-log/diagnostic-sql-reader.d.ts
4
+ type Props$1 = {
5
+ /** SQLite file holding the raw (pre-filter) table. */rawPath: string; /** SQLite file holding the processed (verdict) table. */
6
+ processedPath: string; /** SQLite file holding the connection (lifecycle) table. */
7
+ connectionPath: string;
8
+ };
9
+ type Row = Record<string, unknown>;
10
+ /**
11
+ * Read-only SQL surface over the three diagnostic tables, for Claude to query
12
+ * the log with arbitrary `SELECT`s. It opens all files read-only and exposes
13
+ * three views — `raw`, `processed`, `connection` — that hide the storage
14
+ * details (the physical table is `leuco_log` and each row's columns live
15
+ * inside a JSON `event` blob): the views surface the columns as plain fields,
16
+ * with `payload` already pulled out of the nested JSON.
17
+ *
18
+ * The tables are separate files. `raw` and `processed` share an `event_id`,
19
+ * so a `JOIN` answers "the event arrived, but what verdict did it get?";
20
+ * `connection` answers the other half — "did the listener ever connect at
21
+ * all?". Writes are impossible: the connection is read-only and `query`
22
+ * rejects anything but a single `SELECT`.
23
+ */
24
+ declare class ConnectorDiagnosticSqlReader {
25
+ private readonly db;
26
+ constructor(props: Props$1);
27
+ /**
28
+ * Run one read-only `SELECT` and return the rows. Returns an `Error` (rather
29
+ * than throwing) for a non-SELECT statement or a SQL error, so the caller
30
+ * can surface the message without a stack trace.
31
+ */
32
+ query(sql: string, params?: (string | number | null)[]): Row[] | Error;
33
+ close(): void;
34
+ }
35
+ //#endregion
36
+ //#region lib/services/diagnostics/diagnostic-event.d.ts
37
+ type DiagnosticEvent = {
38
+ seq: number | null;
39
+ ts: number | null;
40
+ type: string;
41
+ outcome: string;
42
+ eventId: string | null;
43
+ payload: string | null;
44
+ payloadParsed: Record<string, unknown> | null;
45
+ preview: string | null;
46
+ };
47
+ type DiagnosticConnectionError = {
48
+ seq: number | null;
49
+ ts: number | null;
50
+ type: string;
51
+ status: string;
52
+ detail: string | null;
53
+ };
54
+ declare const previewOf: (payload: unknown) => string | null;
55
+ declare const toDiagnosticEvent: (row: Record<string, unknown>) => DiagnosticEvent;
56
+ declare const toDiagnosticConnectionError: (row: Record<string, unknown>) => DiagnosticConnectionError;
57
+ declare const queryRows: (reader: ConnectorDiagnosticSqlReader, sql: string, params: (string | number | null)[]) => Record<string, unknown>[] | Error;
58
+ //#endregion
59
+ //#region lib/services/diagnostics/funnel-diagnostics.d.ts
60
+ /** Narrow channel registry — only `list()` is needed. */
61
+ type DiagnosticsChannelSource = {
62
+ list(): ChannelConfig[];
63
+ };
64
+ /** Narrow gateway probe — only the daemon status is needed. */
65
+ type DiagnosticsGatewayProbe = {
66
+ getStatus(): {
67
+ running: boolean;
68
+ pid: number | null;
69
+ port: number;
70
+ };
71
+ };
72
+ /** Narrow token reader — diagnostics only needs to read the token, never to mint or rotate it. */
73
+ type DiagnosticsTokenReader = {
74
+ read(): string | null;
75
+ };
76
+ /** Narrow publisher used only for replay. */
77
+ type DiagnosticsPublisher = {
78
+ publish(channelName: string, request: {
79
+ content: string;
80
+ connector?: string;
81
+ }): Promise<{
82
+ state: "ok";
83
+ offset: number;
84
+ } | {
85
+ state: "offline";
86
+ } | {
87
+ state: "error";
88
+ reason: string;
89
+ }>;
90
+ };
91
+ type Props = {
92
+ gateway: DiagnosticsGatewayProbe;
93
+ gatewayToken: DiagnosticsTokenReader;
94
+ channels: DiagnosticsChannelSource;
95
+ publisher: DiagnosticsPublisher;
96
+ tmpDir: string;
97
+ };
98
+ type DiagnosisStatus = "ok" | "warn" | "error";
99
+ type ChannelDiagnosis = {
100
+ channel: string;
101
+ channelId: string;
102
+ gateway: {
103
+ running: boolean;
104
+ pid: number | null;
105
+ port: number | null;
106
+ uptimeMs: number | null;
107
+ };
108
+ listeners: Array<{
109
+ name: string;
110
+ type: string;
111
+ alive: boolean;
112
+ events: number;
113
+ errors: number;
114
+ lastEventAt: string | null;
115
+ }>;
116
+ claudeClients: number;
117
+ recentEvents: DiagnosticEvent[];
118
+ connectionErrors: DiagnosticConnectionError[];
119
+ diagnosis: {
120
+ status: DiagnosisStatus;
121
+ message: string;
122
+ nextActions: string[];
123
+ rootCause: string | null;
124
+ };
125
+ };
126
+ type DiagnoseAllReport = {
127
+ summary: {
128
+ total: number;
129
+ ok: number;
130
+ warn: number;
131
+ error: number;
132
+ criticalChannels: string[];
133
+ warnChannels: string[];
134
+ suggestedActions: string[];
135
+ };
136
+ channels: ChannelDiagnosis[];
137
+ };
138
+ type ReplayResult = {
139
+ state: "ok";
140
+ seq: number | null;
141
+ offset: number;
142
+ preview: string | null;
143
+ } | {
144
+ state: "offline";
145
+ } | {
146
+ state: "error";
147
+ reason: string;
148
+ } | {
149
+ state: "not-found";
150
+ };
151
+ type StorePaths = {
152
+ rawPath: string;
153
+ processedPath: string;
154
+ connectionPath: string;
155
+ };
156
+ /**
157
+ * Programmable diagnostics surface — used by both the CLI (fnl debug …) and
158
+ * the MCP tools (fnl_debug, fnl_recent_events, …). Pure read-side, no
159
+ * mutation; pair with FunnelRecovery for self-healing actions.
160
+ */
161
+ declare class FunnelDiagnostics {
162
+ private readonly props;
163
+ constructor(props: Props);
164
+ diagnose(channelName?: string): Promise<ChannelDiagnosis | null>;
165
+ diagnoseAll(): Promise<DiagnoseAllReport>;
166
+ recentEvents(channelName: string | null, limit?: number): Promise<DiagnosticEvent[]>;
167
+ droppedEvents(channelName: string | null, limit?: number): Promise<DiagnosticEvent[]>;
168
+ connectionErrors(channelName: string | null, limit?: number): Promise<DiagnosticConnectionError[]>;
169
+ replay(channelName: string, seq?: number): Promise<ReplayResult>;
170
+ resolveStore(): StorePaths | null;
171
+ private resolveChannelId;
172
+ private fetchGatewayStatus;
173
+ private buildChannelDiagnosis;
174
+ }
175
+ //#endregion
176
+ export { DiagnosticsGatewayProbe as a, FunnelDiagnostics as c, DiagnosticEvent as d, previewOf as f, ConnectorDiagnosticSqlReader as g, toDiagnosticEvent as h, DiagnosticsChannelSource as i, ReplayResult as l, toDiagnosticConnectionError as m, DiagnoseAllReport as n, DiagnosticsPublisher as o, queryRows as p, DiagnosisStatus as r, DiagnosticsTokenReader as s, ChannelDiagnosis as t, DiagnosticConnectionError as u };
@@ -0,0 +1,18 @@
1
+ //#region lib/services/docs/funnel-docs.d.ts
2
+ type DocsTopicListing = {
3
+ name: string;
4
+ summary: string;
5
+ };
6
+ /**
7
+ * Programmable docs surface — used by both the CLI (fnl docs <topic>) and the
8
+ * MCP / SDK consumers. Docs are embedded into the build so a Claude session
9
+ * can self-discover funnel's vocabulary without external network access.
10
+ */
11
+ declare class FunnelDocs {
12
+ constructor();
13
+ list(): DocsTopicListing[];
14
+ get(topic: string): string | null;
15
+ topics(): string[];
16
+ }
17
+ //#endregion
18
+ export { FunnelDocs as n, DocsTopicListing as t };