@interactive-inc/claude-funnel 0.66.0 → 0.68.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 (97) hide show
  1. package/README.md +31 -6
  2. package/dist/bin.js +292 -383
  3. package/dist/channel.d.ts +76 -52
  4. package/dist/channel.js +82 -47
  5. package/dist/{channels-DR90RmUP.d.ts → channels-B0cifCfw.d.ts} +6 -6
  6. package/dist/claude.d.ts +11 -8
  7. package/dist/claude.js +31 -15
  8. package/dist/connector-descriptor-D4XtEsH8.d.ts +215 -0
  9. package/dist/{connector-diagnostics-recorder-COtNEmUp.js → connector-diagnostics-recorder-BmOijU1p.js} +1 -0
  10. package/dist/connectors/discord.d.ts +11 -8
  11. package/dist/connectors/discord.js +2 -2
  12. package/dist/connectors/gh.d.ts +7 -6
  13. package/dist/connectors/gh.js +2 -2
  14. package/dist/connectors/schedule.d.ts +21 -17
  15. package/dist/connectors/schedule.js +3 -3
  16. package/dist/connectors/slack.d.ts +15 -10
  17. package/dist/connectors/slack.js +3 -3
  18. package/dist/diagnostic-event-CxMM5Bl2.js +78 -0
  19. package/dist/diagnostic-log-BsAg5qZK.d.ts +186 -0
  20. package/dist/diagnostics.d.ts +2 -2
  21. package/dist/diagnostics.js +3 -2
  22. package/dist/{discord-connector-DXARcO6w.js → discord-connector-CAcWifJn.js} +6 -6
  23. package/dist/{discord-connector-schema-D1dyxhXk.d.ts → discord-connector-schema-BaBZYHxK.d.ts} +0 -1
  24. package/dist/docs.d.ts +1 -1
  25. package/dist/docs.js +1 -1
  26. package/dist/doctor.d.ts +1 -1
  27. package/dist/doctor.js +1 -1
  28. package/dist/event-log.d.ts +2 -0
  29. package/dist/event-log.js +3 -0
  30. package/dist/{file-process-guard-CiBwXjzm.d.ts → file-process-guard-D-QatrwL.d.ts} +22 -18
  31. package/dist/{file-system-DHgbuTFb.d.ts → file-system-BNtl997T.d.ts} +2 -1
  32. package/dist/{flume-source-listener-FzR5lGpL.d.ts → flume-source-listener-Ba8_1Mg-.d.ts} +9 -6
  33. package/dist/{funnel-diagnostics-Cvk6Sk4x.js → funnel-diagnostics-CAG7hJP0.js} +85 -141
  34. package/dist/{funnel-diagnostics-kbd27d7I.d.ts → funnel-diagnostics-CDrZznTB.d.ts} +18 -18
  35. package/dist/{funnel-docs-C-ge0MuB.js → funnel-docs-CI4hMkhq.js} +12 -3
  36. package/dist/{funnel-doctor-CVTNUtDn.d.ts → funnel-doctor-CY4S5cnz.d.ts} +20 -9
  37. package/dist/{funnel-doctor-XrI2GBH8.js → funnel-doctor-Csw5qjcH.js} +1 -0
  38. package/dist/{funnel-error-0t1MK1R6.js → funnel-error-DoSBCogD.js} +13 -0
  39. package/dist/{funnel-recovery-D8MkiTpk.d.ts → funnel-recovery-BwcSNNSN.d.ts} +1 -2
  40. package/dist/{funnel-recovery-DKnEutUS.js → funnel-recovery-DYbSYGWC.js} +1 -0
  41. package/dist/gateway/daemon.js +60 -139
  42. package/dist/gateway.d.ts +2 -3
  43. package/dist/gateway.js +2 -2
  44. package/dist/{gh-connector-BUGCOEWS.js → gh-connector-DMwI6zJA.js} +5 -5
  45. package/dist/{gh-connector-schema-BCHEqpUg.d.ts → gh-connector-schema-BrNsCl1T.d.ts} +0 -1
  46. package/dist/{index-BU1TtuFy.d.ts → index-fHrKn4jm.d.ts} +210 -40
  47. package/dist/index.d.ts +125 -115
  48. package/dist/index.js +242 -57
  49. package/dist/{local-config-json-schema-DexV8vX3.js → local-config-json-schema-_-YVHX4c.js} +4 -4
  50. package/dist/local-config.d.ts +2 -3
  51. package/dist/local-config.js +1 -1
  52. package/dist/logger.d.ts +17 -265
  53. package/dist/logger.js +6 -141
  54. package/dist/memory-event-log-BY8XjFzi.js +147 -0
  55. package/dist/{memory-token-prompter-D0kIdhar.d.ts → memory-token-prompter-B20tl_aU.d.ts} +2 -3
  56. package/dist/{node-file-system-BOXIHW_Q.js → node-file-system-xJx7dymE.js} +11 -3
  57. package/dist/{profiles-M24JrcOh.d.ts → profiles-CePRnv3F.d.ts} +3 -4
  58. package/dist/{profiles-ZHLONml4.js → profiles-KpN7_gBw.js} +19 -5
  59. package/dist/profiles.d.ts +1 -1
  60. package/dist/profiles.js +1 -1
  61. package/dist/recovery.d.ts +1 -1
  62. package/dist/recovery.js +1 -1
  63. package/dist/{schedule-connector-9k3gOIgl.js → schedule-connector-BXUOYgwZ.js} +63 -99
  64. package/dist/schedule-connector-schema-CNV8PgWX.js +137 -0
  65. package/dist/schedule-connector-schema-Tq5kJul-.d.ts +127 -0
  66. package/dist/{settings-reader-BnKIbmsD.d.ts → settings-reader-CyTpbAre.d.ts} +1 -2
  67. package/dist/{settings-schema-IShSMm5G.d.ts → settings-schema-B-CKLuST.d.ts} +0 -1
  68. package/dist/{settings-store-ccv291GV.js → settings-store-ZFWBhOnp.js} +12 -3
  69. package/dist/{slack-connector-CxpWagbT.js → slack-connector-CC3V2VXQ.js} +10 -9
  70. package/dist/{slack-event-processor-xFDG3US0.js → slack-event-processor-Cs_YuI3w.js} +2 -2
  71. package/dist/{slack-event-processor-BDTr62cN.d.ts → slack-event-processor-DmAlq4cV.d.ts} +0 -1
  72. package/dist/{slot-fields-D-pvMgTK.js → slot-fields-eCWizJqS.js} +4 -4
  73. package/dist/{sqlite-diagnostic-log-CwhXE9Qh.js → sqlite-diagnostic-log-Qs4cf9I_.js} +92 -100
  74. package/dist/sqlite-event-log-BYgCFMxC.d.ts +284 -0
  75. package/dist/{funnel-log-sqlite-sink-DLYkY0pZ.js → sqlite-event-log-eZuXKszJ.js} +53 -12
  76. package/dist/{yaml-render-DVEtYuF1.js → yaml-render-DxxRQT-V.js} +3 -3
  77. package/package.json +25 -10
  78. package/dist/broadcaster-DVNcw00K.d.ts +0 -138
  79. package/dist/connector-descriptor-CmyHlEiT.d.ts +0 -380
  80. package/dist/diagnostic-sql-reader-oXZnWFf_.js +0 -83
  81. package/dist/file-system-Wvzc2ePY.js +0 -9
  82. package/dist/logger-B6iyNbxM.js +0 -9
  83. package/dist/schedule-connector-schema-D7ZEKvit.d.ts +0 -49
  84. package/dist/schedule-connector-schema-DKEPZnVv.js +0 -31
  85. package/dist/settings-reader-9FcX3qS1.js +0 -11
  86. /package/dist/{connector-adapter-Dvs8N7ew.js → connector-adapter-BL2ehMej.js} +0 -0
  87. /package/dist/{connector-listener-mPGZYa8e.js → connector-listener-BcQMzY6Q.js} +0 -0
  88. /package/dist/{discord-connector-schema-B4YpWpR3.js → discord-connector-schema-saAGuPmJ.js} +0 -0
  89. /package/dist/{error-message-of-ColuYmAk.js → error-message-of-BG4ezZ_s.js} +0 -0
  90. /package/dist/{funnel-docs-Cd66sKyV.d.ts → funnel-docs-BJWawfWI.d.ts} +0 -0
  91. /package/dist/{gh-connector-schema-CAqIhzGr.js → gh-connector-schema-DmZTrzUu.js} +0 -0
  92. /package/dist/{logger-Bc_Ooypb.d.ts → logger-mMZvblTx.d.ts} +0 -0
  93. /package/dist/{loopback-fetch-CVNuN3YZ.js → loopback-fetch-HP-bo9Kv.js} +0 -0
  94. /package/dist/{node-http-client-u00atiKx.js → node-http-client-SX3xFh4h.js} +0 -0
  95. /package/dist/{node-process-runner-DxTvycoK.js → node-process-runner-D4rzPl8Q.js} +0 -0
  96. /package/dist/{resolve-connector-token-DxDG9mhf.js → resolve-connector-token-BwiJCu82.js} +0 -0
  97. /package/dist/{slack-connector-schema-Dem8to4P.js → slack-connector-schema-EGVuuqSX.js} +0 -0
@@ -1,6 +1,17 @@
1
- import { t as NodeFunnelFileSystem } from "./node-file-system-BOXIHW_Q.js";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-xJx7dymE.js";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
+ //#region lib/engine/claude/session-file-exists.ts
5
+ /**
6
+ * Checks the Claude session path selected by the launch environment and rejects
7
+ * empty jsonl files that Claude itself cannot resume.
8
+ */
9
+ function sessionFileExists(props) {
10
+ const path = join(props.env.CLAUDE_CONFIG_DIR ?? globalThis.process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects", props.cwd.replace(/\//g, "-"), `${props.sessionId}.jsonl`);
11
+ if (!props.fs.existsSync(path)) return false;
12
+ return props.fs.readFileSync(path).trim().length > 0;
13
+ }
14
+ //#endregion
4
15
  //#region lib/engine/profiles/profiles.ts
5
16
  const defaultFs = new NodeFunnelFileSystem();
6
17
  /**
@@ -106,9 +117,12 @@ var FunnelProfiles = class {
106
117
  * bad resume.
107
118
  */
108
119
  sessionFileExists(cwd, sessionId, env) {
109
- const path = join(env.CLAUDE_CONFIG_DIR ?? globalThis.process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects", cwd.replace(/\//g, "-"), `${sessionId}.jsonl`);
110
- if (!this.fs.existsSync(path)) return false;
111
- return this.fs.readFileSync(path).trim().length > 0;
120
+ return sessionFileExists({
121
+ fs: this.fs,
122
+ cwd,
123
+ sessionId,
124
+ env
125
+ });
112
126
  }
113
127
  update(name, fields) {
114
128
  this.store.update((settings) => {
@@ -126,4 +140,4 @@ var FunnelProfiles = class {
126
140
  }
127
141
  };
128
142
  //#endregion
129
- export { FunnelProfiles as t };
143
+ export { sessionFileExists as n, FunnelProfiles as t };
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-M24JrcOh.js";
1
+ import { t as FunnelProfiles } from "./profiles-CePRnv3F.js";
2
2
  export { FunnelProfiles };
package/dist/profiles.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as FunnelProfiles } from "./profiles-ZHLONml4.js";
1
+ import { t as FunnelProfiles } from "./profiles-KpN7_gBw.js";
2
2
  export { FunnelProfiles };
@@ -1,2 +1,2 @@
1
- import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-D8MkiTpk.js";
1
+ import { a as RecoveryListenerControl, i as RecoveryGatewayControl, n as RecoveryAction, o as RecoveryResult, r as RecoveryChannelSource, t as FunnelRecovery } from "./funnel-recovery-BwcSNNSN.js";
2
2
  export { FunnelRecovery, RecoveryAction, RecoveryChannelSource, RecoveryGatewayControl, RecoveryListenerControl, RecoveryResult };
package/dist/recovery.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as FunnelRecovery } from "./funnel-recovery-DKnEutUS.js";
1
+ import { t as FunnelRecovery } from "./funnel-recovery-DYbSYGWC.js";
2
2
  export { FunnelRecovery };
@@ -1,85 +1,10 @@
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";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-xJx7dymE.js";
2
+ import { t as errorMessageOf } from "./error-message-of-BG4ezZ_s.js";
3
+ import { t as FunnelConnectorListener } from "./connector-listener-BcQMzY6Q.js";
4
+ import { a as scheduleConnectorSchema, c as matchCron, o as scheduleEntryInputSchema, s as scheduleEntrySchema } from "./schedule-connector-schema-CNV8PgWX.js";
5
+ import { t as FunnelConnectorDiagnosticsRecorder } from "./connector-diagnostics-recorder-BmOijU1p.js";
6
6
  import { dirname, join } from "node:path";
7
7
  import { z } from "zod";
8
- //#region lib/engine/connectors/match-cron.ts
9
- const parseField = (expr, min, max) => {
10
- const values = /* @__PURE__ */ new Set();
11
- for (const part of expr.split(",")) {
12
- const [rangePart, stepPart] = part.split("/");
13
- const step = stepPart ? Number(stepPart) : 1;
14
- if (!Number.isFinite(step) || step <= 0) throw new Error(`invalid cron step: "${stepPart}"`);
15
- let lo = min;
16
- let hi = max;
17
- if (rangePart === "*" || rangePart === void 0 || rangePart === "") {
18
- lo = min;
19
- hi = max;
20
- } else if (rangePart.includes("-")) {
21
- const [aStr, bStr] = rangePart.split("-");
22
- const a = Number(aStr);
23
- const b = Number(bStr);
24
- if (!Number.isFinite(a) || !Number.isFinite(b)) throw new Error(`invalid cron range: "${rangePart}"`);
25
- lo = a;
26
- hi = b;
27
- } else {
28
- const n = Number(rangePart);
29
- if (!Number.isFinite(n)) throw new Error(`invalid cron value: "${rangePart}"`);
30
- lo = n;
31
- hi = stepPart ? max : n;
32
- }
33
- if (lo < min || hi > max || lo > hi) throw new Error(`cron value out of range: ${rangePart} (must be ${min}-${max})`);
34
- for (let i = lo; i <= hi; i += step) values.add(i);
35
- }
36
- return {
37
- min,
38
- max,
39
- values
40
- };
41
- };
42
- /**
43
- * Returns true when `date` (local time) satisfies a 5-field cron expression.
44
- *
45
- * Two deliberate deviations from Vixie cron, called out so schedules are not
46
- * written expecting the other behavior:
47
- * - Day-of-month and day-of-week are ANDed, not ORed. Vixie cron fires when
48
- * EITHER matches once both are restricted; here every field must match.
49
- * - Day-of-week is 0-6 (Sunday=0). `7` for Sunday is NOT accepted (it throws
50
- * as out-of-range); use `0`.
51
- */
52
- const matchCron = (expr, date) => {
53
- const parts = expr.trim().split(/\s+/);
54
- if (parts.length !== 5) throw new Error(`cron must have 5 fields (got ${parts.length}): "${expr}"`);
55
- const [minute, hour, dom, month, dow] = parts;
56
- if (!minute || !hour || !dom || !month || !dow) throw new Error(`cron has empty fields: "${expr}"`);
57
- const fields = [
58
- {
59
- field: parseField(minute, 0, 59),
60
- value: date.getMinutes()
61
- },
62
- {
63
- field: parseField(hour, 0, 23),
64
- value: date.getHours()
65
- },
66
- {
67
- field: parseField(dom, 1, 31),
68
- value: date.getDate()
69
- },
70
- {
71
- field: parseField(month, 1, 12),
72
- value: date.getMonth() + 1
73
- },
74
- {
75
- field: parseField(dow, 0, 6),
76
- value: date.getDay()
77
- }
78
- ];
79
- for (const { field, value } of fields) if (!field.values.has(value)) return false;
80
- return true;
81
- };
82
- //#endregion
83
8
  //#region lib/engine/connectors/schedule-state-store.ts
84
9
  const defaultFs = new NodeFunnelFileSystem();
85
10
  /**
@@ -190,16 +115,35 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
190
115
  async tick(notify) {
191
116
  const now = this.truncateToMinute(this.now());
192
117
  const state = this.lastFiredStore.load();
193
- let changed = false;
118
+ const changes = [this.pruneRemovedEntries(state)];
194
119
  for (const entry of this.config.entries) {
195
120
  if (!entry.enabled) continue;
196
- if (await this.fireEntry(entry, now, state, notify)) changed = true;
121
+ const fired = await this.fireEntry(entry, now, state, notify);
122
+ changes.push(fired);
197
123
  }
198
- if (changed) this.lastFiredStore.save(state);
124
+ if (changes.includes(true)) this.lastFiredStore.save(state);
199
125
  }
200
126
  async fireEntry(entry, now, state, notify) {
127
+ if (entry.kind === "once") return this.fireOnceEntry(entry, now, state, notify);
128
+ return this.fireCronEntry(entry, now, state, notify);
129
+ }
130
+ async fireOnceEntry(entry, now, state, notify) {
131
+ if (state.has(entry.id)) return false;
132
+ const runAt = new Date(entry.runAt);
133
+ if (now.getTime() < runAt.getTime()) return false;
134
+ const lateness = now.getTime() - runAt.getTime();
135
+ if (entry.catchupPolicy === "skip" && lateness >= 6e4) {
136
+ state.set(entry.id, runAt);
137
+ return true;
138
+ }
139
+ await this.notifyOne(entry, runAt, notify, lateness > 0);
140
+ state.set(entry.id, runAt);
141
+ return true;
142
+ }
143
+ async fireCronEntry(entry, now, state, notify) {
201
144
  const lastFired = state.get(entry.id);
202
- const searchFrom = lastFired ? new Date(lastFired.getTime() + 6e4) : now;
145
+ const createdAt = entry.createdAt ? this.truncateToMinute(new Date(entry.createdAt)) : now;
146
+ const searchFrom = lastFired ? new Date(lastFired.getTime() + 6e4) : createdAt;
203
147
  if (searchFrom.getTime() > now.getTime()) return false;
204
148
  if (entry.catchupPolicy === "skip") {
205
149
  try {
@@ -214,30 +158,38 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
214
158
  }
215
159
  if (entry.catchupPolicy === "all") {
216
160
  const matches = this.findAllMatches(entry.cron, searchFrom, now, entry.id);
217
- if (matches.length === 0) return false;
161
+ if (matches.length === 0) return this.seedSearchWatermark(entry.id, lastFired, now, state);
218
162
  for (const match of matches) await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime());
219
163
  state.set(entry.id, matches[matches.length - 1] ?? now);
220
164
  return true;
221
165
  }
222
166
  const match = this.findMostRecentMatch(entry.cron, searchFrom, now, entry.id);
223
- if (!match) return false;
167
+ if (!match) return this.seedSearchWatermark(entry.id, lastFired, now, state);
224
168
  await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime());
225
169
  state.set(entry.id, match);
226
170
  return true;
227
171
  }
172
+ seedSearchWatermark(entryId, lastFired, now, state) {
173
+ if (lastFired !== void 0) return false;
174
+ state.set(entryId, now);
175
+ return true;
176
+ }
228
177
  async notifyOne(entry, firedAt, notify, catchup) {
229
178
  const meta = {
230
179
  event_type: "schedule",
231
180
  schedule_id: entry.id,
232
- cron: entry.cron,
181
+ schedule_kind: entry.kind,
233
182
  fired_at: firedAt.toISOString(),
234
183
  catchup_policy: entry.catchupPolicy
235
184
  };
185
+ if (entry.kind === "cron") meta.cron = entry.cron;
186
+ else meta.run_at = entry.runAt;
236
187
  if (catchup) meta.catchup = "true";
237
188
  const eventId = `${entry.id}@${firedAt.toISOString()}`;
238
189
  this.diagnostics.recordRaw(eventId, JSON.stringify({
239
190
  schedule_id: entry.id,
240
- cron: entry.cron,
191
+ kind: entry.kind,
192
+ ...entry.kind === "cron" ? { cron: entry.cron } : { run_at: entry.runAt },
241
193
  prompt: entry.prompt,
242
194
  fired_at: firedAt.toISOString(),
243
195
  catchup
@@ -308,16 +260,19 @@ var FunnelScheduleListener = class extends FunnelConnectorListener {
308
260
  copy.setSeconds(0, 0);
309
261
  return copy;
310
262
  }
263
+ pruneRemovedEntries(state) {
264
+ const configuredIds = new Set(this.config.entries.map((entry) => entry.id));
265
+ const removedIds = [];
266
+ for (const id of state.keys()) {
267
+ if (configuredIds.has(id)) continue;
268
+ state.delete(id);
269
+ removedIds.push(id);
270
+ }
271
+ return removedIds.length > 0;
272
+ }
311
273
  };
312
274
  //#endregion
313
275
  //#region lib/engine/connectors/schedule-connector.ts
314
- const addEntryArgsSchema = z.object({
315
- cron: z.string(),
316
- prompt: z.string(),
317
- id: z.string().optional(),
318
- enabled: z.boolean().optional(),
319
- catchupPolicy: scheduleEntrySchema.shape.catchupPolicy.optional()
320
- });
321
276
  const removeEntryArgsSchema = z.object({ id: z.string() });
322
277
  /**
323
278
  * Schedule connector descriptor. Pass `scheduleConnector()` to
@@ -375,11 +330,20 @@ const scheduleConnector = (options = {}) => ({
375
330
  },
376
331
  addEntry(props) {
377
332
  const parsed = scheduleConnectorSchema.parse(props.config);
378
- const args = addEntryArgsSchema.parse(props.args);
333
+ const args = scheduleEntryInputSchema.parse(props.args);
334
+ const id = args.id ?? props.context.generateId();
335
+ if (parsed.entries.some((entry) => entry.id === id)) throw new Error(`schedule entry "${id}" already exists`);
379
336
  const entry = scheduleEntrySchema.parse({
380
- id: args.id ?? props.context.generateId(),
381
- cron: args.cron,
337
+ id,
338
+ ..."cron" in args ? {
339
+ kind: "cron",
340
+ cron: args.cron
341
+ } : {
342
+ kind: "once",
343
+ runAt: args.runAt
344
+ },
382
345
  prompt: args.prompt,
346
+ createdAt: props.context.now,
383
347
  ...args.enabled !== void 0 ? { enabled: args.enabled } : {},
384
348
  ...args.catchupPolicy !== void 0 ? { catchupPolicy: args.catchupPolicy } : {}
385
349
  });
@@ -408,4 +372,4 @@ const scheduleConnector = (options = {}) => ({
408
372
  }
409
373
  });
410
374
  //#endregion
411
- export { matchCron as i, FunnelScheduleListener as n, FunnelScheduleStateStore as r, scheduleConnector as t };
375
+ export { FunnelScheduleListener as n, FunnelScheduleStateStore as r, scheduleConnector as t };
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ //#region lib/engine/connectors/match-cron.ts
3
+ const parseField = (expr, min, max) => {
4
+ const values = /* @__PURE__ */ new Set();
5
+ for (const part of expr.split(",")) {
6
+ if (part.length === 0) throw new Error(`invalid empty cron list item: "${expr}"`);
7
+ const stepSegments = part.split("/");
8
+ if (stepSegments.length > 2) throw new Error(`invalid cron step: "${part}"`);
9
+ const rangePart = stepSegments[0];
10
+ const stepPart = stepSegments[1];
11
+ if (stepSegments.length === 2 && stepPart === "") throw new Error(`invalid cron step: "${part}"`);
12
+ const step = stepPart ? Number(stepPart) : 1;
13
+ if (!Number.isInteger(step) || step <= 0) throw new Error(`invalid cron step: "${stepPart}"`);
14
+ let lo = min;
15
+ let hi = max;
16
+ if (rangePart === "*" || rangePart === void 0 || rangePart === "") {
17
+ lo = min;
18
+ hi = max;
19
+ } else if (rangePart.includes("-")) {
20
+ const rangeSegments = rangePart.split("-");
21
+ if (rangeSegments.length !== 2) throw new Error(`invalid cron range: "${rangePart}"`);
22
+ const aStr = rangeSegments[0];
23
+ const bStr = rangeSegments[1];
24
+ const a = Number(aStr);
25
+ const b = Number(bStr);
26
+ if (!Number.isInteger(a) || !Number.isInteger(b)) throw new Error(`invalid cron range: "${rangePart}"`);
27
+ lo = a;
28
+ hi = b;
29
+ } else {
30
+ const n = Number(rangePart);
31
+ if (!Number.isInteger(n)) throw new Error(`invalid cron value: "${rangePart}"`);
32
+ lo = n;
33
+ hi = stepPart ? max : n;
34
+ }
35
+ if (lo < min || hi > max || lo > hi) throw new Error(`cron value out of range: ${rangePart} (must be ${min}-${max})`);
36
+ for (let i = lo; i <= hi; i += step) values.add(i);
37
+ }
38
+ return {
39
+ min,
40
+ max,
41
+ values
42
+ };
43
+ };
44
+ const parseCron = (expr) => {
45
+ const parts = expr.trim().split(/\s+/);
46
+ if (parts.length !== 5) throw new Error(`cron must have 5 fields (got ${parts.length}): "${expr}"`);
47
+ const [minute, hour, dom, month, dow] = parts;
48
+ if (!minute || !hour || !dom || !month || !dow) throw new Error(`cron has empty fields: "${expr}"`);
49
+ return {
50
+ minute: parseField(minute, 0, 59),
51
+ hour: parseField(hour, 0, 23),
52
+ dayOfMonth: parseField(dom, 1, 31),
53
+ month: parseField(month, 1, 12),
54
+ dayOfWeek: parseField(dow, 0, 7),
55
+ dayOfMonthRestricted: dom !== "*",
56
+ dayOfWeekRestricted: dow !== "*"
57
+ };
58
+ };
59
+ /** Throws with a user-facing reason when `expr` is not a supported cron expression. */
60
+ const validateCronExpression = (expr) => {
61
+ parseCron(expr);
62
+ };
63
+ /**
64
+ * Returns true when `date` (local time) satisfies a standard 5-field cron
65
+ * expression. Sunday accepts both 0 and 7. When day-of-month and day-of-week
66
+ * are both restricted, either field matching is sufficient (Vixie cron).
67
+ */
68
+ const matchCron = (expr, date) => {
69
+ const cron = parseCron(expr);
70
+ if (!cron.minute.values.has(date.getMinutes())) return false;
71
+ if (!cron.hour.values.has(date.getHours())) return false;
72
+ if (!cron.month.values.has(date.getMonth() + 1)) return false;
73
+ const dayOfMonthMatches = cron.dayOfMonth.values.has(date.getDate());
74
+ const day = date.getDay();
75
+ const dayOfWeekMatches = cron.dayOfWeek.values.has(day) || day === 0 && cron.dayOfWeek.values.has(7);
76
+ if (cron.dayOfMonthRestricted && cron.dayOfWeekRestricted) return dayOfMonthMatches || dayOfWeekMatches;
77
+ return dayOfMonthMatches && dayOfWeekMatches;
78
+ };
79
+ //#endregion
80
+ //#region lib/engine/connectors/schedule-connector-schema.ts
81
+ /**
82
+ * Catch-up behavior when the daemon was down past one or more matching minutes.
83
+ *
84
+ * - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
85
+ * - `all`: fire once per missed minute, oldest first (capped at 24 h).
86
+ * - `skip`: never fire missed matches; only fire when the current minute matches.
87
+ */
88
+ const scheduleCatchupPolicySchema = z.enum([
89
+ "latest",
90
+ "all",
91
+ "skip"
92
+ ]);
93
+ const cronExpressionSchema = z.string().superRefine((expression, context) => {
94
+ try {
95
+ validateCronExpression(expression);
96
+ } catch (error) {
97
+ context.addIssue({
98
+ code: "custom",
99
+ message: error instanceof Error ? error.message : String(error)
100
+ });
101
+ }
102
+ });
103
+ const scheduleEntryBaseSchema = z.object({
104
+ id: z.string(),
105
+ prompt: z.string(),
106
+ enabled: z.boolean().default(true),
107
+ catchupPolicy: scheduleCatchupPolicySchema.default("latest"),
108
+ createdAt: z.string().datetime().optional()
109
+ });
110
+ const scheduleEntryInputBaseSchema = z.object({
111
+ id: z.string().optional(),
112
+ prompt: z.string(),
113
+ enabled: z.boolean().optional(),
114
+ catchupPolicy: scheduleCatchupPolicySchema.optional()
115
+ });
116
+ const cronScheduleEntryInputSchema = scheduleEntryInputBaseSchema.extend({ cron: cronExpressionSchema }).strict();
117
+ const onceScheduleEntryInputSchema = scheduleEntryInputBaseSchema.extend({ runAt: z.string().datetime({ offset: true }) }).strict();
118
+ const scheduleEntryInputSchema = z.union([onceScheduleEntryInputSchema, cronScheduleEntryInputSchema]);
119
+ const cronScheduleEntrySchema = scheduleEntryBaseSchema.extend({
120
+ kind: z.literal("cron").default("cron"),
121
+ cron: cronExpressionSchema
122
+ });
123
+ const onceScheduleEntrySchema = scheduleEntryBaseSchema.extend({
124
+ kind: z.literal("once"),
125
+ runAt: z.string().datetime({ offset: true })
126
+ });
127
+ const scheduleEntrySchema = z.union([onceScheduleEntrySchema, cronScheduleEntrySchema]);
128
+ const scheduleConnectorSchema = z.object({
129
+ id: z.string(),
130
+ name: z.string(),
131
+ type: z.literal("schedule"),
132
+ entries: z.array(scheduleEntrySchema).default([]),
133
+ createdAt: z.string().datetime().optional(),
134
+ updatedAt: z.string().datetime().optional()
135
+ });
136
+ //#endregion
137
+ export { scheduleConnectorSchema as a, matchCron as c, scheduleCatchupPolicySchema as i, validateCronExpression as l, cronScheduleEntrySchema as n, scheduleEntryInputSchema as o, onceScheduleEntrySchema as r, scheduleEntrySchema as s, cronExpressionSchema as t };
@@ -0,0 +1,127 @@
1
+ import { z } from "zod";
2
+ //#region lib/engine/connectors/schedule-connector-schema.d.ts
3
+ /**
4
+ * Catch-up behavior when the daemon was down past one or more matching minutes.
5
+ *
6
+ * - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
7
+ * - `all`: fire once per missed minute, oldest first (capped at 24 h).
8
+ * - `skip`: never fire missed matches; only fire when the current minute matches.
9
+ */
10
+ declare const scheduleCatchupPolicySchema: z.ZodEnum<{
11
+ latest: "latest";
12
+ all: "all";
13
+ skip: "skip";
14
+ }>;
15
+ type ScheduleCatchupPolicy = z.infer<typeof scheduleCatchupPolicySchema>;
16
+ declare const cronExpressionSchema: z.ZodString;
17
+ declare const scheduleEntryInputSchema: z.ZodUnion<readonly [z.ZodObject<{
18
+ id: z.ZodOptional<z.ZodString>;
19
+ prompt: z.ZodString;
20
+ enabled: z.ZodOptional<z.ZodBoolean>;
21
+ catchupPolicy: z.ZodOptional<z.ZodEnum<{
22
+ latest: "latest";
23
+ all: "all";
24
+ skip: "skip";
25
+ }>>;
26
+ runAt: z.ZodString;
27
+ }, z.core.$strict>, z.ZodObject<{
28
+ id: z.ZodOptional<z.ZodString>;
29
+ prompt: z.ZodString;
30
+ enabled: z.ZodOptional<z.ZodBoolean>;
31
+ catchupPolicy: z.ZodOptional<z.ZodEnum<{
32
+ latest: "latest";
33
+ all: "all";
34
+ skip: "skip";
35
+ }>>;
36
+ cron: z.ZodString;
37
+ }, z.core.$strict>]>;
38
+ type ScheduleEntryInput = z.input<typeof scheduleEntryInputSchema>;
39
+ declare const cronScheduleEntrySchema: z.ZodObject<{
40
+ id: z.ZodString;
41
+ prompt: z.ZodString;
42
+ enabled: z.ZodDefault<z.ZodBoolean>;
43
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
44
+ latest: "latest";
45
+ all: "all";
46
+ skip: "skip";
47
+ }>>;
48
+ createdAt: z.ZodOptional<z.ZodString>;
49
+ kind: z.ZodDefault<z.ZodLiteral<"cron">>;
50
+ cron: z.ZodString;
51
+ }, z.core.$strip>;
52
+ declare const onceScheduleEntrySchema: z.ZodObject<{
53
+ id: z.ZodString;
54
+ prompt: z.ZodString;
55
+ enabled: z.ZodDefault<z.ZodBoolean>;
56
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
57
+ latest: "latest";
58
+ all: "all";
59
+ skip: "skip";
60
+ }>>;
61
+ createdAt: z.ZodOptional<z.ZodString>;
62
+ kind: z.ZodLiteral<"once">;
63
+ runAt: z.ZodString;
64
+ }, z.core.$strip>;
65
+ declare const scheduleEntrySchema: z.ZodUnion<readonly [z.ZodObject<{
66
+ id: z.ZodString;
67
+ prompt: z.ZodString;
68
+ enabled: z.ZodDefault<z.ZodBoolean>;
69
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
70
+ latest: "latest";
71
+ all: "all";
72
+ skip: "skip";
73
+ }>>;
74
+ createdAt: z.ZodOptional<z.ZodString>;
75
+ kind: z.ZodLiteral<"once">;
76
+ runAt: z.ZodString;
77
+ }, z.core.$strip>, z.ZodObject<{
78
+ id: z.ZodString;
79
+ prompt: z.ZodString;
80
+ enabled: z.ZodDefault<z.ZodBoolean>;
81
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
82
+ latest: "latest";
83
+ all: "all";
84
+ skip: "skip";
85
+ }>>;
86
+ createdAt: z.ZodOptional<z.ZodString>;
87
+ kind: z.ZodDefault<z.ZodLiteral<"cron">>;
88
+ cron: z.ZodString;
89
+ }, z.core.$strip>]>;
90
+ type ScheduleEntry = z.infer<typeof scheduleEntrySchema>;
91
+ type CronScheduleEntry = z.infer<typeof cronScheduleEntrySchema>;
92
+ type OnceScheduleEntry = z.infer<typeof onceScheduleEntrySchema>;
93
+ declare const scheduleConnectorSchema: z.ZodObject<{
94
+ id: z.ZodString;
95
+ name: z.ZodString;
96
+ type: z.ZodLiteral<"schedule">;
97
+ entries: z.ZodDefault<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
98
+ id: z.ZodString;
99
+ prompt: z.ZodString;
100
+ enabled: z.ZodDefault<z.ZodBoolean>;
101
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
102
+ latest: "latest";
103
+ all: "all";
104
+ skip: "skip";
105
+ }>>;
106
+ createdAt: z.ZodOptional<z.ZodString>;
107
+ kind: z.ZodLiteral<"once">;
108
+ runAt: z.ZodString;
109
+ }, z.core.$strip>, z.ZodObject<{
110
+ id: z.ZodString;
111
+ prompt: z.ZodString;
112
+ enabled: z.ZodDefault<z.ZodBoolean>;
113
+ catchupPolicy: z.ZodDefault<z.ZodEnum<{
114
+ latest: "latest";
115
+ all: "all";
116
+ skip: "skip";
117
+ }>>;
118
+ createdAt: z.ZodOptional<z.ZodString>;
119
+ kind: z.ZodDefault<z.ZodLiteral<"cron">>;
120
+ cron: z.ZodString;
121
+ }, z.core.$strip>]>>>;
122
+ createdAt: z.ZodOptional<z.ZodString>;
123
+ updatedAt: z.ZodOptional<z.ZodString>;
124
+ }, z.core.$strip>;
125
+ type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>;
126
+ //#endregion
127
+ export { ScheduleEntry as a, cronScheduleEntrySchema as c, scheduleConnectorSchema as d, scheduleEntryInputSchema as f, ScheduleConnectorConfig as i, onceScheduleEntrySchema as l, OnceScheduleEntry as n, ScheduleEntryInput as o, scheduleEntrySchema as p, ScheduleCatchupPolicy as r, cronExpressionSchema as s, CronScheduleEntry as t, scheduleCatchupPolicySchema as u };
@@ -1,5 +1,4 @@
1
- import { a as Settings } from "./settings-schema-IShSMm5G.js";
2
-
1
+ import { a as Settings } from "./settings-schema-B-CKLuST.js";
3
2
  //#region lib/engine/id/id-generator.d.ts
4
3
  /**
5
4
  * ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
-
3
2
  //#region lib/engine/settings/settings-schema.d.ts
4
3
  /**
5
4
  * Routing mode when multiple WS clients are subscribed to the same channel.
@@ -1,8 +1,14 @@
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";
1
+ import { t as NodeFunnelFileSystem } from "./node-file-system-xJx7dymE.js";
3
2
  import { dirname, join } from "node:path";
4
3
  import { homedir } from "node:os";
5
4
  import { z } from "zod";
5
+ //#region lib/engine/id/id-generator.ts
6
+ /**
7
+ * ID generator boundary. Default NodeFunnelIdGenerator wraps `crypto.randomUUID()`;
8
+ * MemoryFunnelIdGenerator emits `<prefix>-1, <prefix>-2, ...` for deterministic tests.
9
+ */
10
+ var FunnelIdGenerator = class {};
11
+ //#endregion
6
12
  //#region lib/engine/id/node-id-generator.ts
7
13
  var NodeFunnelIdGenerator = class extends FunnelIdGenerator {
8
14
  generate() {
@@ -10,6 +16,9 @@ var NodeFunnelIdGenerator = class extends FunnelIdGenerator {
10
16
  }
11
17
  };
12
18
  //#endregion
19
+ //#region lib/engine/settings/settings-reader.ts
20
+ var FunnelSettingsReader = class {};
21
+ //#endregion
13
22
  //#region lib/engine/connectors/base-connector-config.ts
14
23
  /**
15
24
  * Fields every connector config carries, regardless of type. The discriminated
@@ -240,4 +249,4 @@ var FunnelSettingsStore = class extends FunnelSettingsReader {
240
249
  }
241
250
  };
242
251
  //#endregion
243
- export { expandHomeDir as a, SETTINGS_VERSION as c, profileConfigSchema as d, settingsSchema as f, SETTINGS_PATH as i, channelConfigSchema as l, NodeFunnelIdGenerator as m, FUNNEL_DIR as n, resolveFunnelDir as o, baseConnectorConfigSchema as p, FunnelSettingsStore as r, resolveFunnelPort as s, DEFAULT_GATEWAY_PORT as t, channelDeliveryModeSchema as u };
252
+ export { expandHomeDir as a, SETTINGS_VERSION as c, profileConfigSchema as d, settingsSchema as f, FunnelIdGenerator as g, NodeFunnelIdGenerator as h, SETTINGS_PATH as i, channelConfigSchema as l, FunnelSettingsReader as m, FUNNEL_DIR as n, resolveFunnelDir as o, baseConnectorConfigSchema as p, FunnelSettingsStore as r, resolveFunnelPort as s, DEFAULT_GATEWAY_PORT as t, channelDeliveryModeSchema as u };
@@ -1,11 +1,11 @@
1
- import { t as NodeFunnelHttpClient } from "./node-http-client-u00atiKx.js";
2
- import { t as FunnelAuthFailedError } from "./funnel-error-0t1MK1R6.js";
3
- import { t as errorMessageOf } from "./error-message-of-ColuYmAk.js";
4
- import { t as slackConnectorSchema } from "./slack-connector-schema-Dem8to4P.js";
5
- import { t as FunnelConnectorAdapter } from "./connector-adapter-Dvs8N7ew.js";
6
- import { t as FunnelSlackEventProcessor } from "./slack-event-processor-xFDG3US0.js";
7
- import { t as resolveConnectorToken } from "./resolve-connector-token-DxDG9mhf.js";
8
- import { i as resolveFlumeDeps, n as FunnelFlumeSourceListener, r as flumeLogHandler, t as slotFields } from "./slot-fields-D-pvMgTK.js";
1
+ import { t as NodeFunnelHttpClient } from "./node-http-client-SX3xFh4h.js";
2
+ import { t as FunnelAuthFailedError } from "./funnel-error-DoSBCogD.js";
3
+ import { t as errorMessageOf } from "./error-message-of-BG4ezZ_s.js";
4
+ import { t as slackConnectorSchema } from "./slack-connector-schema-EGVuuqSX.js";
5
+ import { t as FunnelConnectorAdapter } from "./connector-adapter-BL2ehMej.js";
6
+ import { t as FunnelSlackEventProcessor } from "./slack-event-processor-Cs_YuI3w.js";
7
+ import { t as resolveConnectorToken } from "./resolve-connector-token-BwiJCu82.js";
8
+ import { i as resolveFlumeDeps, n as FunnelFlumeSourceListener, r as flumeLogHandler, t as slotFields } from "./slot-fields-eCWizJqS.js";
9
9
  import { z } from "zod";
10
10
  import { FlumeSlackSource } from "@interactive-inc/flume/slack";
11
11
  //#region lib/engine/connectors/slack-adapter.ts
@@ -289,7 +289,8 @@ var FunnelFlumeSlackListener = class extends FunnelFlumeSourceListener {
289
289
  name: "eyes"
290
290
  }).toString()
291
291
  });
292
- const parsed = parseSlackResponse(await res.text());
292
+ const text = await res.text();
293
+ const parsed = parseSlackResponse(text);
293
294
  if (!parsed.ok) throw new Error(`slack reactions.add: ${parsed.error ?? `status=${res.status}`}`);
294
295
  }
295
296
  };
@@ -83,8 +83,8 @@ const minifySlackEvent = (event) => {
83
83
  };
84
84
  //#endregion
85
85
  //#region lib/engine/connectors/slack-event-processor.ts
86
- const ALLOWED_EVENTS = new Set(["message", "app_mention"]);
87
- const ALLOWED_SUBTYPES = new Set([
86
+ const ALLOWED_EVENTS = /* @__PURE__ */ new Set(["message", "app_mention"]);
87
+ const ALLOWED_SUBTYPES = /* @__PURE__ */ new Set([
88
88
  void 0,
89
89
  "thread_broadcast",
90
90
  "bot_message",
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
-
3
2
  //#region lib/engine/connectors/slack-connector-schema.d.ts
4
3
  /**
5
4
  * A slack connector resolves its tokens one of two ways, set at sync time: