@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.
Files changed (88) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.js +428 -761
  3. package/dist/{channels-2g_BU1N0.d.ts → channels-CRGb6B5_.d.ts} +17 -16
  4. package/dist/claude.d.ts +5 -7
  5. package/dist/claude.js +143 -36
  6. package/dist/{connector-descriptor-6SXJoszo.d.ts → connector-descriptor-BFIhyTfa.d.ts} +49 -10
  7. package/dist/connector-diagnostics-recorder-COtNEmUp.js +42 -0
  8. package/dist/connectors/discord.d.ts +31 -37
  9. package/dist/connectors/discord.js +3 -3
  10. package/dist/connectors/gh.d.ts +37 -33
  11. package/dist/connectors/gh.js +3 -3
  12. package/dist/connectors/schedule.d.ts +9 -57
  13. package/dist/connectors/schedule.js +3 -3
  14. package/dist/connectors/slack.d.ts +106 -132
  15. package/dist/connectors/slack.js +4 -3
  16. package/dist/diagnostics.d.ts +1 -1
  17. package/dist/diagnostics.js +1 -1
  18. package/dist/discord-connector-DIFkYBbi.js +250 -0
  19. package/dist/discord-connector-schema-D-bOVAKt.d.ts +22 -0
  20. package/dist/docs.js +1 -1
  21. package/dist/doctor.d.ts +1 -1
  22. package/dist/doctor.js +1 -1
  23. package/dist/{file-process-guard-C_PLxfUX.d.ts → file-process-guard-tVcgckH6.d.ts} +6 -6
  24. package/dist/{file-system-o51IsM0W.d.ts → file-system-VhwwXZbm.d.ts} +8 -0
  25. package/dist/flume-source-listener-BNyAII7N.d.ts +133 -0
  26. package/dist/{funnel-diagnostics-CSiJmPlZ.js → funnel-diagnostics-Cvk6Sk4x.js} +193 -43
  27. package/dist/{funnel-diagnostics-DpXOsCty.d.ts → funnel-diagnostics-b9ar0Ing.d.ts} +67 -5
  28. package/dist/{funnel-docs-BxXZ9Ksx.js → funnel-docs-C-ge0MuB.js} +42 -6
  29. package/dist/{funnel-doctor-CZf_0Luq.d.ts → funnel-doctor-CnRQi4kM.d.ts} +2 -2
  30. package/dist/{funnel-doctor-DiJCjHsg.js → funnel-doctor-XrI2GBH8.js} +1 -1
  31. package/dist/funnel-error-0t1MK1R6.js +75 -0
  32. package/dist/{funnel-recovery-DnLrdWO9.d.ts → funnel-recovery-CMhY8Jfk.d.ts} +1 -1
  33. package/dist/gateway/daemon.js +167 -527
  34. package/dist/gateway.d.ts +3 -3
  35. package/dist/gateway.js +3 -3
  36. package/dist/gh-connector-BUGCOEWS.js +187 -0
  37. package/dist/{gh-connector-schema-Rzwc1c1N.js → gh-connector-schema-CAqIhzGr.js} +7 -0
  38. package/dist/gh-connector-schema-DWQaB6gX.d.ts +16 -0
  39. package/dist/{index-CgY8NdMz.d.ts → index-Ds6sHhA-.d.ts} +37 -19
  40. package/dist/index.d.ts +182 -22
  41. package/dist/index.js +363 -173
  42. package/dist/{local-config-json-schema-JyLqOQNX.js → local-config-json-schema-DexV8vX3.js} +24 -4
  43. package/dist/local-config.d.ts +39 -2
  44. package/dist/local-config.js +53 -2
  45. package/dist/logger.js +1 -1
  46. package/dist/loopback-fetch-CVNuN3YZ.js +40 -0
  47. package/dist/{local-config-sync-Dh1Croqe.d.ts → memory-token-prompter-BoV8Hf-n.d.ts} +30 -3
  48. package/dist/node-file-system-BOXIHW_Q.js +174 -0
  49. package/dist/{profiles-DSzTeKQw.js → profiles-ZHLONml4.js} +49 -49
  50. package/dist/{profiles-Cy5wXQ0L.d.ts → profiles-cVZQkM69.d.ts} +3 -3
  51. package/dist/profiles.d.ts +1 -1
  52. package/dist/profiles.js +1 -1
  53. package/dist/recovery.d.ts +1 -1
  54. package/dist/recovery.js +1 -1
  55. package/dist/resolve-connector-token-DxDG9mhf.js +22 -0
  56. package/dist/{schedule-connector-L4uzg5M8.js → schedule-connector-9k3gOIgl.js} +54 -55
  57. package/dist/schedule-connector-schema-Z0RXLgPI.d.ts +49 -0
  58. package/dist/settings-reader-BNxjsxCB.d.ts +27 -0
  59. package/dist/{settings-store-CUKSeTXC.js → settings-store-C2QdOH-t.js} +23 -4
  60. package/dist/slack-connector-CxpWagbT.js +388 -0
  61. package/dist/slack-event-processor-BhCf5Wiy.d.ts +95 -0
  62. package/dist/slack-event-processor-xFDG3US0.js +176 -0
  63. package/dist/slot-fields-D-pvMgTK.js +249 -0
  64. package/dist/{memory-diagnostic-log-CI60kNfB.js → sqlite-diagnostic-log-DOTPW-tG.js} +373 -249
  65. package/dist/{yaml-render-93pX7EF7.js → yaml-render--J1_3BSA.js} +25 -21
  66. package/package.json +2 -4
  67. package/dist/discord-connector-BL36yvbL.js +0 -250
  68. package/dist/gateway-base-url-Dy4Ykuoh.js +0 -14
  69. package/dist/gh-connector-DpiixfQZ.js +0 -226
  70. package/dist/http-client-oICicjuO.d.ts +0 -18
  71. package/dist/memory-token-prompter-B4sjyaAq.d.ts +0 -57
  72. package/dist/memory-token-prompter-CZde7e6y.js +0 -61
  73. package/dist/node-file-system-Blr8pAir.js +0 -48
  74. package/dist/settings-reader-BIFB_j2f.d.ts +0 -18
  75. package/dist/slack-connector-DQIFPdBF.js +0 -484
  76. package/dist/slot-fields-CMoRpwuy.js +0 -45
  77. /package/dist/{connector-adapter-DU9Rvyec.js → connector-adapter-Dvs8N7ew.js} +0 -0
  78. /package/dist/{connector-listener-DR3aKOuK.js → connector-listener-mPGZYa8e.js} +0 -0
  79. /package/dist/{diagnostic-sql-reader-C9zR-Csp.js → diagnostic-sql-reader-oXZnWFf_.js} +0 -0
  80. /package/dist/{discord-connector-schema-B_N6IXLz.js → discord-connector-schema-B4YpWpR3.js} +0 -0
  81. /package/dist/{error-message-of-Byi4y0Uf.js → error-message-of-ColuYmAk.js} +0 -0
  82. /package/dist/{funnel-log-sqlite-sink-kqJbx2H7.js → funnel-log-sqlite-sink-DLYkY0pZ.js} +0 -0
  83. /package/dist/{funnel-recovery-BFdPjL6Z.js → funnel-recovery-DKnEutUS.js} +0 -0
  84. /package/dist/{node-http-client-lowp60Oa.js → node-http-client-u00atiKx.js} +0 -0
  85. /package/dist/{schedule-connector-schema-CfyuMCMh.js → schedule-connector-schema-DKEPZnVv.js} +0 -0
  86. /package/dist/{settings-reader-CtQ-Ix8_.js → settings-reader-9FcX3qS1.js} +0 -0
  87. /package/dist/{settings-schema-D1xcOqRu.d.ts → settings-schema-BL_c2Udm.d.ts} +0 -0
  88. /package/dist/{slack-connector-schema-C1zEf4TG.js → slack-connector-schema-Dem8to4P.js} +0 -0
@@ -1,7 +1,8 @@
1
- import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
2
- import { t as FunnelConnectorListener } from "./connector-listener-DR3aKOuK.js";
3
- import { n as scheduleConnectorSchema, r as scheduleEntrySchema } from "./schedule-connector-schema-CfyuMCMh.js";
4
- import { t as errorMessageOf } from "./error-message-of-Byi4y0Uf.js";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
2
+ import { t as errorMessageOf } from "./error-message-of-ColuYmAk.js";
3
+ import { t as FunnelConnectorListener } from "./connector-listener-mPGZYa8e.js";
4
+ import { n as scheduleConnectorSchema, r as scheduleEntrySchema } from "./schedule-connector-schema-DKEPZnVv.js";
5
+ import { t as FunnelConnectorDiagnosticsRecorder } from "./connector-diagnostics-recorder-COtNEmUp.js";
5
6
  import { dirname, join } from "node:path";
6
7
  import { z } from "zod";
7
8
  //#region lib/engine/connectors/match-cron.ts
@@ -87,7 +88,7 @@ const defaultFs = new NodeFunnelFileSystem();
87
88
  * connectorDir) so this store does not know about the funnel directory layout
88
89
  * (`channels/<id>/connectors/<id>/state.json` lives outside this class).
89
90
  */
90
- var ScheduleStateStore = class {
91
+ var FunnelScheduleStateStore = class {
91
92
  path;
92
93
  fs;
93
94
  constructor(deps) {
@@ -116,50 +117,75 @@ const MAX_CATCHUP_MINUTES = 1440;
116
117
  var FunnelScheduleListener = class extends FunnelConnectorListener {
117
118
  config;
118
119
  lastFiredStore;
119
- channelId;
120
120
  logger;
121
- diagnosticLog;
121
+ diagnostics;
122
122
  now;
123
123
  onFired;
124
124
  timer = null;
125
125
  stopped = false;
126
+ tickScheduled = false;
126
127
  constructor(deps) {
127
128
  super();
128
129
  this.config = deps.config;
129
130
  this.lastFiredStore = deps.lastFiredStore;
130
- this.channelId = deps.channelId ?? null;
131
131
  this.logger = deps.logger;
132
- this.diagnosticLog = deps.diagnosticLog;
132
+ this.diagnostics = new FunnelConnectorDiagnosticsRecorder({
133
+ type: "schedule",
134
+ connectorId: deps.config.id,
135
+ channelId: deps.channelId ?? null,
136
+ log: deps.diagnosticLog
137
+ });
133
138
  this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
134
139
  this.onFired = deps.onFired ?? null;
135
140
  }
136
141
  async start(notify) {
137
142
  this.stopped = false;
138
- this.recordConnection("started", "");
143
+ this.tickScheduled = true;
144
+ this.diagnostics.recordConnection("started", "");
139
145
  const scheduleNext = () => {
140
146
  if (this.stopped) return;
141
147
  const date = this.now();
142
148
  const msUntilNextMinute = 6e4 - (date.getSeconds() * 1e3 + date.getMilliseconds());
143
149
  this.timer = setTimeout(async () => {
144
150
  if (this.stopped) return;
145
- await this.tick(notify);
151
+ try {
152
+ await this.tick(notify);
153
+ } catch (error) {
154
+ this.recordTickError(error);
155
+ }
146
156
  scheduleNext();
147
157
  }, msUntilNextMinute);
148
158
  this.timer.unref();
159
+ this.tickScheduled = true;
149
160
  };
150
- await this.tick(notify);
161
+ try {
162
+ await this.tick(notify);
163
+ } catch (error) {
164
+ this.recordTickError(error);
165
+ }
166
+ this.diagnostics.recordConnection("connected", "");
151
167
  scheduleNext();
152
168
  }
153
169
  async stop() {
154
170
  this.stopped = true;
171
+ this.tickScheduled = false;
155
172
  if (this.timer) {
156
173
  clearTimeout(this.timer);
157
174
  this.timer = null;
158
175
  }
159
- this.recordConnection("stopped", "");
176
+ this.diagnostics.recordConnection("disconnected", "");
177
+ this.diagnostics.recordConnection("stopped", "");
178
+ }
179
+ recordTickError(error) {
180
+ const message = errorMessageOf(error);
181
+ this.diagnostics.recordConnection("error", `tick: ${message}`);
182
+ this.logger?.error("schedule tick failed", {
183
+ connector: this.config.name,
184
+ error: message
185
+ });
160
186
  }
161
187
  isAlive() {
162
- return !this.stopped && this.timer !== null;
188
+ return !this.stopped && this.tickScheduled;
163
189
  }
164
190
  async tick(notify) {
165
191
  const now = this.truncateToMinute(this.now());
@@ -209,14 +235,20 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
209
235
  };
210
236
  if (catchup) meta.catchup = "true";
211
237
  const eventId = `${entry.id}@${firedAt.toISOString()}`;
212
- this.recordRaw(eventId, entry, firedAt, catchup);
238
+ this.diagnostics.recordRaw(eventId, JSON.stringify({
239
+ schedule_id: entry.id,
240
+ cron: entry.cron,
241
+ prompt: entry.prompt,
242
+ fired_at: firedAt.toISOString(),
243
+ catchup
244
+ }));
213
245
  try {
214
246
  await notify(entry.prompt, meta);
215
247
  } catch (error) {
216
- this.recordProcessed(eventId, entry, "emitted:delivery-failed");
248
+ this.diagnostics.recordProcessed(eventId, "emitted:delivery-failed", entry.prompt);
217
249
  throw error;
218
250
  }
219
- this.recordProcessed(eventId, entry, "emitted");
251
+ this.diagnostics.recordProcessed(eventId, "emitted", entry.prompt);
220
252
  if (this.onFired) try {
221
253
  await this.onFired(entry, firedAt);
222
254
  } catch (error) {
@@ -263,7 +295,7 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
263
295
  }
264
296
  logInvalidCron(entry, error) {
265
297
  const message = errorMessageOf(error);
266
- this.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
298
+ this.diagnostics.recordConnection("error", `invalid cron "${entry.cron}" (entry ${entry.id}): ${message}`);
267
299
  this.logger?.error("invalid cron expression in schedule", {
268
300
  connector: this.config.name,
269
301
  id: entry.id,
@@ -276,40 +308,6 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
276
308
  copy.setSeconds(0, 0);
277
309
  return copy;
278
310
  }
279
- recordRaw(eventId, entry, firedAt, catchup) {
280
- this.diagnosticLog?.recordRaw({
281
- eventId,
282
- type: "schedule",
283
- connectorId: this.config.id,
284
- channelId: this.channelId,
285
- payload: JSON.stringify({
286
- schedule_id: entry.id,
287
- cron: entry.cron,
288
- prompt: entry.prompt,
289
- fired_at: firedAt.toISOString(),
290
- catchup
291
- })
292
- });
293
- }
294
- recordProcessed(eventId, entry, outcome) {
295
- this.diagnosticLog?.recordProcessed({
296
- eventId,
297
- type: "schedule",
298
- connectorId: this.config.id,
299
- channelId: this.channelId,
300
- outcome,
301
- payload: entry.prompt
302
- });
303
- }
304
- recordConnection(status, detail) {
305
- this.diagnosticLog?.recordConnection({
306
- type: "schedule",
307
- connectorId: this.config.id,
308
- channelId: this.channelId,
309
- status,
310
- detail
311
- });
312
- }
313
311
  };
314
312
  //#endregion
315
313
  //#region lib/engine/connectors/schedule-connector.ts
@@ -335,14 +333,15 @@ const scheduleConnector = (options = {}) => ({
335
333
  const parsed = scheduleConnectorSchema.parse(config);
336
334
  return new FunnelScheduleListener({
337
335
  config: parsed,
338
- lastFiredStore: new ScheduleStateStore({
336
+ lastFiredStore: new FunnelScheduleStateStore({
339
337
  path: join(deps.connectorDir(deps.channelId, parsed.id), "state.json"),
340
338
  fs: deps.fs
341
339
  }),
342
340
  channelId: deps.channelId,
343
341
  logger: deps.logger,
344
342
  diagnosticLog: deps.diagnosticLog,
345
- onFired: options.onFired
343
+ onFired: options.onFired,
344
+ now: () => deps.clock.now()
346
345
  });
347
346
  },
348
347
  createAdapter: null,
@@ -409,4 +408,4 @@ const scheduleConnector = (options = {}) => ({
409
408
  }
410
409
  });
411
410
  //#endregion
412
- export { matchCron as i, FunnelScheduleListener as n, ScheduleStateStore as r, scheduleConnector as t };
411
+ export { matchCron as i, FunnelScheduleListener as n, FunnelScheduleStateStore as r, scheduleConnector as t };
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+
3
+ //#region lib/engine/connectors/schedule-connector-schema.d.ts
4
+ /**
5
+ * Catch-up behavior when the daemon was down past one or more matching minutes.
6
+ *
7
+ * - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
8
+ * - `all`: fire once per missed minute, oldest first (capped at 24 h).
9
+ * - `skip`: never fire missed matches; only fire when the current minute matches.
10
+ */
11
+ declare const scheduleCatchupPolicySchema: z.ZodEnum<{
12
+ latest: "latest";
13
+ all: "all";
14
+ skip: "skip";
15
+ }>;
16
+ type ScheduleCatchupPolicy = z.infer<typeof scheduleCatchupPolicySchema>;
17
+ declare const scheduleEntrySchema: z.ZodObject<{
18
+ id: z.ZodString;
19
+ cron: z.ZodString;
20
+ prompt: z.ZodString;
21
+ enabled: z.ZodDefault<z.ZodBoolean>;
22
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
23
+ latest: "latest";
24
+ all: "all";
25
+ skip: "skip";
26
+ }>>;
27
+ }, z.core.$strip>;
28
+ type ScheduleEntry = z.infer<typeof scheduleEntrySchema>;
29
+ declare const scheduleConnectorSchema: z.ZodObject<{
30
+ id: z.ZodString;
31
+ name: z.ZodString;
32
+ type: z.ZodLiteral<"schedule">;
33
+ entries: z.ZodDefault<z.ZodArray<z.ZodObject<{
34
+ id: z.ZodString;
35
+ cron: z.ZodString;
36
+ prompt: z.ZodString;
37
+ enabled: z.ZodDefault<z.ZodBoolean>;
38
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
39
+ latest: "latest";
40
+ all: "all";
41
+ skip: "skip";
42
+ }>>;
43
+ }, z.core.$strip>>>;
44
+ createdAt: z.ZodOptional<z.ZodString>;
45
+ updatedAt: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>;
48
+ //#endregion
49
+ export { scheduleConnectorSchema as a, scheduleCatchupPolicySchema as i, ScheduleConnectorConfig as n, scheduleEntrySchema as o, ScheduleEntry as r, ScheduleCatchupPolicy as t };
@@ -0,0 +1,27 @@
1
+ import { a as Settings } from "./settings-schema-BL_c2Udm.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
+ * Atomic read-modify-write. Implementations must serialize against
18
+ * concurrent processes touching the same file (the Node store does so via
19
+ * an exclusive lockfile; Memory stores are single-threaded). Engine
20
+ * classes must use `update` for any mutation that depends on prior state,
21
+ * otherwise a concurrent CLI invocation or `fnl claude` launch can lose
22
+ * the edit through a read-modify-write race.
23
+ */
24
+ abstract update<T>(mutator: (settings: Settings) => T): T;
25
+ }
26
+ //#endregion
27
+ export { FunnelIdGenerator as n, FunnelSettingsReader as t };
@@ -1,5 +1,5 @@
1
- import { t as NodeFunnelFileSystem } from "./node-file-system-Blr8pAir.js";
2
- import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-CtQ-Ix8_.js";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
2
+ import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-9FcX3qS1.js";
3
3
  import { dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { z } from "zod";
@@ -29,11 +29,11 @@ const baseConnectorConfigSchema = z.object({
29
29
  //#region lib/engine/settings/settings-schema.ts
30
30
  /**
31
31
  * Connectors are stored loosely here: settings validates only the common base
32
- * fields and preserves every type-specific key verbatim (`.passthrough()`).
32
+ * fields and preserves every type-specific key verbatim (`.loose()`).
33
33
  * Core does not enumerate connector types, so strict per-type validation happens
34
34
  * at the registry/descriptor layer (CRUD time), not on every settings read.
35
35
  */
36
- const storedConnectorSchema = baseConnectorConfigSchema.passthrough();
36
+ const storedConnectorSchema = baseConnectorConfigSchema.loose();
37
37
  /**
38
38
  * Routing mode when multiple WS clients are subscribed to the same channel.
39
39
  *
@@ -192,6 +192,25 @@ var FunnelSettingsStore = class extends FunnelSettingsReader {
192
192
  };
193
193
  this.fs.writeSecretFileSync(this.path, `${JSON.stringify(versioned, null, 2)}\n`);
194
194
  }
195
+ /**
196
+ * Run `mutator` against a freshly-read settings object inside an exclusive
197
+ * file lock, then persist the result. Use this instead of bare `read()` +
198
+ * `write()` for any logical edit (add channel, set token, rename profile),
199
+ * so two concurrent CLI invocations or `fnl claude` launches cannot lose
200
+ * each other's updates via a read-modify-write race. The mutator may
201
+ * mutate `settings` in place and/or return a value; the value is returned
202
+ * to the caller. A thrown error from the mutator skips the write but still
203
+ * releases the lock.
204
+ */
205
+ update(mutator) {
206
+ this.fs.mkdirSync(dirname(this.path), { recursive: true });
207
+ return this.fs.withFileLock(`${this.path}.lock`, () => {
208
+ const settings = this.read();
209
+ const result = mutator(settings);
210
+ this.write(settings);
211
+ return result;
212
+ });
213
+ }
195
214
  };
196
215
  //#endregion
197
216
  export { resolveFunnelDir as a, channelConfigSchema as c, settingsSchema as d, baseConnectorConfigSchema as f, SETTINGS_PATH as i, channelDeliveryModeSchema as l, FUNNEL_DIR as n, resolveFunnelPort as o, NodeFunnelIdGenerator as p, FunnelSettingsStore as r, SETTINGS_VERSION as s, DEFAULT_GATEWAY_PORT as t, profileConfigSchema as u };