@interactive-inc/claude-funnel 0.10.0 → 0.10.1

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 (228) hide show
  1. package/dist/bin.js +448 -448
  2. package/dist/connectors/slack.d.ts +1 -29
  3. package/dist/gateway/daemon.js +166 -166
  4. package/dist/index.d.ts +4 -11
  5. package/dist/index.js +133 -120
  6. package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
  7. package/package.json +1 -6
  8. package/dist/slack-connector-schema-D7zAHN8k.d.ts +0 -15
  9. package/lib/bin.ts +0 -3
  10. package/lib/cli/factory.ts +0 -10
  11. package/lib/cli/index.ts +0 -85
  12. package/lib/cli/router/query-to-cli-args.ts +0 -20
  13. package/lib/cli/router/to-request.ts +0 -113
  14. package/lib/cli/router/validator.ts +0 -27
  15. package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +0 -27
  16. package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +0 -40
  17. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +0 -41
  18. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +0 -22
  19. package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +0 -23
  20. package/lib/cli/routes/channels.$channel.connectors.$connector.ts +0 -26
  21. package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +0 -92
  22. package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +0 -22
  23. package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +0 -63
  24. package/lib/cli/routes/channels.$channel.connectors.ts +0 -26
  25. package/lib/cli/routes/channels.$channel.publish.ts +0 -52
  26. package/lib/cli/routes/channels.$channel.rename.$newName.ts +0 -22
  27. package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +0 -34
  28. package/lib/cli/routes/channels.$channel.ts +0 -34
  29. package/lib/cli/routes/channels.add.$channel.ts +0 -33
  30. package/lib/cli/routes/channels.remove.$channel.ts +0 -20
  31. package/lib/cli/routes/channels.ts +0 -39
  32. package/lib/cli/routes/claude.ts +0 -70
  33. package/lib/cli/routes/gateway.listeners.ts +0 -41
  34. package/lib/cli/routes/gateway.logs.ts +0 -123
  35. package/lib/cli/routes/gateway.restart.ts +0 -50
  36. package/lib/cli/routes/gateway.run.ts +0 -41
  37. package/lib/cli/routes/gateway.start.ts +0 -50
  38. package/lib/cli/routes/gateway.status.ts +0 -19
  39. package/lib/cli/routes/gateway.stop.ts +0 -32
  40. package/lib/cli/routes/gateway.ts +0 -55
  41. package/lib/cli/routes/index.ts +0 -219
  42. package/lib/cli/routes/profiles.$profile.as-default.ts +0 -22
  43. package/lib/cli/routes/profiles.$profile.rename.$newName.ts +0 -22
  44. package/lib/cli/routes/profiles.$profile.run.ts +0 -36
  45. package/lib/cli/routes/profiles.add.$profile.ts +0 -49
  46. package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
  47. package/lib/cli/routes/profiles.set.$profile.ts +0 -45
  48. package/lib/cli/routes/profiles.ts +0 -40
  49. package/lib/cli/routes/status.ts +0 -93
  50. package/lib/cli/routes/update.ts +0 -27
  51. package/lib/connectors/connector-adapter.ts +0 -9
  52. package/lib/connectors/connector-config-schema.ts +0 -16
  53. package/lib/connectors/connector-factory.ts +0 -94
  54. package/lib/connectors/connector-listener.ts +0 -20
  55. package/lib/connectors/discord-adapter.ts +0 -51
  56. package/lib/connectors/discord-connector-schema.ts +0 -12
  57. package/lib/connectors/discord-event-processor.ts +0 -48
  58. package/lib/connectors/discord-listener.ts +0 -111
  59. package/lib/connectors/discord.ts +0 -4
  60. package/lib/connectors/gh-adapter.ts +0 -48
  61. package/lib/connectors/gh-connector-schema.ts +0 -12
  62. package/lib/connectors/gh-listener.ts +0 -137
  63. package/lib/connectors/gh.ts +0 -3
  64. package/lib/connectors/match-cron.ts +0 -78
  65. package/lib/connectors/schedule-connector-schema.ts +0 -33
  66. package/lib/connectors/schedule-listener.ts +0 -207
  67. package/lib/connectors/schedule-state-store.ts +0 -54
  68. package/lib/connectors/schedule.ts +0 -4
  69. package/lib/connectors/slack-adapter.ts +0 -36
  70. package/lib/connectors/slack-connector-schema.ts +0 -13
  71. package/lib/connectors/slack-event-processor.ts +0 -97
  72. package/lib/connectors/slack-listener.ts +0 -97
  73. package/lib/connectors/slack.ts +0 -4
  74. package/lib/engine/channels/channels.ts +0 -520
  75. package/lib/engine/claude/claude.ts +0 -205
  76. package/lib/engine/claude/gateway-controller.ts +0 -4
  77. package/lib/engine/fs/file-system.ts +0 -23
  78. package/lib/engine/fs/memory-file-system.ts +0 -102
  79. package/lib/engine/fs/node-file-system.ts +0 -68
  80. package/lib/engine/http/http-client.ts +0 -17
  81. package/lib/engine/http/memory-http-client.ts +0 -36
  82. package/lib/engine/http/node-http-client.ts +0 -23
  83. package/lib/engine/id/id-generator.ts +0 -7
  84. package/lib/engine/id/memory-id-generator.ts +0 -20
  85. package/lib/engine/id/node-id-generator.ts +0 -7
  86. package/lib/engine/logger/logger.ts +0 -11
  87. package/lib/engine/logger/memory-logger.ts +0 -28
  88. package/lib/engine/logger/node-logger.ts +0 -49
  89. package/lib/engine/logger/noop-logger.ts +0 -9
  90. package/lib/engine/mcp/channel-server.ts +0 -123
  91. package/lib/engine/mcp/channel-subscriber.ts +0 -82
  92. package/lib/engine/mcp/mcp.ts +0 -126
  93. package/lib/engine/mcp/read-channel-connectors.ts +0 -34
  94. package/lib/engine/mcp/read-gateway-token.ts +0 -16
  95. package/lib/engine/mcp/usage-hint-for-type.ts +0 -15
  96. package/lib/engine/process/memory-process-runner.ts +0 -88
  97. package/lib/engine/process/node-process-runner.ts +0 -91
  98. package/lib/engine/process/process-runner.ts +0 -33
  99. package/lib/engine/profiles/profile-channel-checker.ts +0 -7
  100. package/lib/engine/profiles/profiles.ts +0 -126
  101. package/lib/engine/settings/mock-settings-reader.ts +0 -27
  102. package/lib/engine/settings/settings-reader.ts +0 -6
  103. package/lib/engine/settings/settings-schema.ts +0 -48
  104. package/lib/engine/settings/settings-store.ts +0 -110
  105. package/lib/engine/time/clock.ts +0 -15
  106. package/lib/engine/time/memory-clock.ts +0 -26
  107. package/lib/engine/time/node-clock.ts +0 -7
  108. package/lib/funnel.ts +0 -294
  109. package/lib/gateway/auth-middleware.ts +0 -44
  110. package/lib/gateway/broadcaster.ts +0 -319
  111. package/lib/gateway/channel-publisher.ts +0 -67
  112. package/lib/gateway/daemon.ts +0 -47
  113. package/lib/gateway/factory.ts +0 -10
  114. package/lib/gateway/funnel-event-store.ts +0 -155
  115. package/lib/gateway/gateway-server.ts +0 -426
  116. package/lib/gateway/gateway-token.ts +0 -79
  117. package/lib/gateway/gateway.ts +0 -209
  118. package/lib/gateway/kill-competing-slack-gateways.ts +0 -56
  119. package/lib/gateway/listener-supervisor.ts +0 -339
  120. package/lib/gateway/listeners-client.ts +0 -128
  121. package/lib/gateway/publish-schema.ts +0 -27
  122. package/lib/gateway/resolve-daemon-script.ts +0 -26
  123. package/lib/gateway/routes/channels.connectors.call.ts +0 -39
  124. package/lib/gateway/routes/channels.publish.ts +0 -44
  125. package/lib/gateway/routes/health.ts +0 -13
  126. package/lib/gateway/routes/index.ts +0 -26
  127. package/lib/gateway/routes/listeners.list.ts +0 -6
  128. package/lib/gateway/routes/listeners.restart.ts +0 -15
  129. package/lib/gateway/routes/listeners.start.ts +0 -15
  130. package/lib/gateway/routes/listeners.stop.ts +0 -15
  131. package/lib/gateway/routes/route-deps.ts +0 -19
  132. package/lib/gateway/routes/status.ts +0 -15
  133. package/lib/gateway/routes/validator.ts +0 -17
  134. package/lib/index.ts +0 -67
  135. package/lib/logger/leuco-human-file-writer.ts +0 -65
  136. package/lib/logger/leuco-human-logger.ts +0 -98
  137. package/lib/logger/leuco-human-record.ts +0 -16
  138. package/lib/logger/leuco-human-stdout-writer.ts +0 -26
  139. package/lib/logger/leuco-human-writer.ts +0 -14
  140. package/lib/logger/leuco-logger-memory-sink.ts +0 -67
  141. package/lib/logger/leuco-logger-record.ts +0 -13
  142. package/lib/logger/leuco-logger-sink.ts +0 -33
  143. package/lib/logger/leuco-logger-sqlite-sink.ts +0 -355
  144. package/lib/logger/leuco-logger.ts +0 -135
  145. package/lib/tui/app.tsx +0 -357
  146. package/lib/tui/components/add-row.tsx +0 -18
  147. package/lib/tui/components/brand.tsx +0 -27
  148. package/lib/tui/components/card.tsx +0 -44
  149. package/lib/tui/components/detail-bar.tsx +0 -46
  150. package/lib/tui/components/editable-field.tsx +0 -33
  151. package/lib/tui/components/empty-state.tsx +0 -11
  152. package/lib/tui/components/gateway-status.tsx +0 -66
  153. package/lib/tui/components/keymap.tsx +0 -29
  154. package/lib/tui/components/menu-item.tsx +0 -73
  155. package/lib/tui/components/menu.tsx +0 -26
  156. package/lib/tui/components/panel-header.tsx +0 -22
  157. package/lib/tui/components/readonly-field.tsx +0 -18
  158. package/lib/tui/components/section-header.tsx +0 -25
  159. package/lib/tui/components/selection-accent.tsx +0 -32
  160. package/lib/tui/components/session-item.tsx +0 -33
  161. package/lib/tui/components/session-list.tsx +0 -33
  162. package/lib/tui/components/ui/hascii/accordion-item.tsx +0 -88
  163. package/lib/tui/components/ui/hascii/accordion.tsx +0 -96
  164. package/lib/tui/components/ui/hascii/alert-dialog.tsx +0 -43
  165. package/lib/tui/components/ui/hascii/badge.tsx +0 -51
  166. package/lib/tui/components/ui/hascii/breadcrumb.tsx +0 -58
  167. package/lib/tui/components/ui/hascii/button.tsx +0 -194
  168. package/lib/tui/components/ui/hascii/card-content.tsx +0 -14
  169. package/lib/tui/components/ui/hascii/card-description.tsx +0 -13
  170. package/lib/tui/components/ui/hascii/card-footer.tsx +0 -14
  171. package/lib/tui/components/ui/hascii/card-header.tsx +0 -14
  172. package/lib/tui/components/ui/hascii/card-title.tsx +0 -13
  173. package/lib/tui/components/ui/hascii/card.tsx +0 -27
  174. package/lib/tui/components/ui/hascii/checkbox.tsx +0 -65
  175. package/lib/tui/components/ui/hascii/command.tsx +0 -159
  176. package/lib/tui/components/ui/hascii/dialog-content.tsx +0 -14
  177. package/lib/tui/components/ui/hascii/dialog-description.tsx +0 -13
  178. package/lib/tui/components/ui/hascii/dialog-footer.tsx +0 -14
  179. package/lib/tui/components/ui/hascii/dialog-header.tsx +0 -14
  180. package/lib/tui/components/ui/hascii/dialog-title.tsx +0 -13
  181. package/lib/tui/components/ui/hascii/dialog.tsx +0 -27
  182. package/lib/tui/components/ui/hascii/file-tree.tsx +0 -142
  183. package/lib/tui/components/ui/hascii/focus-group.tsx +0 -62
  184. package/lib/tui/components/ui/hascii/form-item.tsx +0 -43
  185. package/lib/tui/components/ui/hascii/input-otp.tsx +0 -86
  186. package/lib/tui/components/ui/hascii/input.tsx +0 -130
  187. package/lib/tui/components/ui/hascii/pagination.tsx +0 -105
  188. package/lib/tui/components/ui/hascii/progress.tsx +0 -28
  189. package/lib/tui/components/ui/hascii/select.tsx +0 -131
  190. package/lib/tui/components/ui/hascii/separator.tsx +0 -35
  191. package/lib/tui/components/ui/hascii/sidebar-content.tsx +0 -23
  192. package/lib/tui/components/ui/hascii/sidebar-header.tsx +0 -14
  193. package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +0 -67
  194. package/lib/tui/components/ui/hascii/sidebar.tsx +0 -24
  195. package/lib/tui/components/ui/hascii/skeleton.tsx +0 -60
  196. package/lib/tui/components/ui/hascii/slider.tsx +0 -91
  197. package/lib/tui/components/ui/hascii/snackbar.tsx +0 -75
  198. package/lib/tui/components/ui/hascii/sparkline.tsx +0 -53
  199. package/lib/tui/components/ui/hascii/spinner.tsx +0 -47
  200. package/lib/tui/components/ui/hascii/stepper.tsx +0 -54
  201. package/lib/tui/components/ui/hascii/switch.tsx +0 -66
  202. package/lib/tui/components/ui/hascii/table.tsx +0 -95
  203. package/lib/tui/components/ui/hascii/tabs.tsx +0 -59
  204. package/lib/tui/components/ui/hascii/toggle-group-item.tsx +0 -45
  205. package/lib/tui/components/ui/hascii/toggle-group.tsx +0 -99
  206. package/lib/tui/components/ui/hascii/tree.tsx +0 -104
  207. package/lib/tui/components/view-shell.tsx +0 -44
  208. package/lib/tui/filter-input.tsx +0 -33
  209. package/lib/tui/hooks/hascii/use-pressable.ts +0 -54
  210. package/lib/tui/parse-comma-list.ts +0 -14
  211. package/lib/tui/profile-launcher.tsx +0 -61
  212. package/lib/tui/scrollbar-options.ts +0 -19
  213. package/lib/tui/sidebar.tsx +0 -50
  214. package/lib/tui/theme.ts +0 -40
  215. package/lib/tui/tui.tsx +0 -20
  216. package/lib/tui/types.ts +0 -38
  217. package/lib/tui/unique-name.ts +0 -18
  218. package/lib/tui/use-event-stream.ts +0 -133
  219. package/lib/tui/use-snapshot.ts +0 -99
  220. package/lib/tui/utils/hascii/form-item-context.tsx +0 -23
  221. package/lib/tui/utils/hascii/input-focus-context.tsx +0 -31
  222. package/lib/tui/utils/hascii/theme-context.tsx +0 -26
  223. package/lib/tui/utils/hascii/theme.ts +0 -176
  224. package/lib/tui/views/channels-view.tsx +0 -108
  225. package/lib/tui/views/connectors-view.tsx +0 -164
  226. package/lib/tui/views/events-view.tsx +0 -160
  227. package/lib/tui/views/listeners-view.tsx +0 -80
  228. package/lib/tui/views/profiles-view.tsx +0 -152
@@ -1,207 +0,0 @@
1
- import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
2
- import { matchCron } from "@/connectors/match-cron"
3
- import { ScheduleStateStore } from "@/connectors/schedule-state-store"
4
- import { FunnelLogger } from "@/engine/logger/logger"
5
- import { NodeFunnelLogger } from "@/engine/logger/node-logger"
6
- import type { ScheduleConnectorConfig, ScheduleEntry } from "@/connectors/schedule-connector-schema"
7
-
8
- type Deps = {
9
- config: ScheduleConnectorConfig
10
- lastFiredStore: ScheduleStateStore
11
- logger?: FunnelLogger
12
- now?: () => Date
13
- }
14
-
15
- const defaultLogger = new NodeFunnelLogger()
16
-
17
- const MAX_CATCHUP_MINUTES = 60 * 24
18
-
19
- export class FunnelScheduleListener extends FunnelConnectorListener {
20
- private readonly config: ScheduleConnectorConfig
21
- private readonly lastFiredStore: ScheduleStateStore
22
- private readonly logger: FunnelLogger
23
- private readonly now: () => Date
24
- private timer: ReturnType<typeof setTimeout> | null = null
25
- private stopped = false
26
-
27
- constructor(deps: Deps) {
28
- super()
29
- this.config = deps.config
30
- this.lastFiredStore = deps.lastFiredStore
31
- this.logger = deps.logger ?? defaultLogger
32
- this.now = deps.now ?? (() => new Date())
33
- }
34
-
35
- async start(notify: NotifyFn): Promise<void> {
36
- this.stopped = false
37
-
38
- const scheduleNext = () => {
39
- if (this.stopped) return
40
-
41
- const date = this.now()
42
- const msUntilNextMinute = 60_000 - (date.getSeconds() * 1000 + date.getMilliseconds())
43
- this.timer = setTimeout(async () => {
44
- if (this.stopped) return
45
- await this.tick(notify)
46
- scheduleNext()
47
- }, msUntilNextMinute)
48
-
49
- this.timer.unref()
50
- }
51
-
52
- await this.tick(notify)
53
- scheduleNext()
54
- }
55
-
56
- async stop(): Promise<void> {
57
- this.stopped = true
58
-
59
- if (this.timer) {
60
- clearTimeout(this.timer)
61
- this.timer = null
62
- }
63
- }
64
-
65
- override isAlive(): boolean {
66
- return !this.stopped && this.timer !== null
67
- }
68
-
69
- async tick(notify: NotifyFn): Promise<void> {
70
- const now = this.truncateToMinute(this.now())
71
- const state = this.lastFiredStore.load()
72
- let changed = false
73
-
74
- for (const entry of this.config.entries) {
75
- if (!entry.enabled) continue
76
-
77
- const fired = await this.fireEntry(entry, now, state, notify)
78
-
79
- if (fired) changed = true
80
- }
81
-
82
- if (changed) this.lastFiredStore.save(state)
83
- }
84
-
85
- private async fireEntry(
86
- entry: ScheduleEntry,
87
- now: Date,
88
- state: Map<string, Date>,
89
- notify: NotifyFn,
90
- ): Promise<boolean> {
91
- const lastFired = state.get(entry.id)
92
- const searchFrom = lastFired ? new Date(lastFired.getTime() + 60_000) : now
93
-
94
- if (searchFrom.getTime() > now.getTime()) return false
95
-
96
- if (entry.catchupPolicy === "skip") {
97
- try {
98
- if (!matchCron(entry.cron, now)) return false
99
- } catch (error) {
100
- this.logInvalidCron(entry, error)
101
- return false
102
- }
103
-
104
- await this.notifyOne(entry, now, notify, false)
105
- state.set(entry.id, now)
106
- return true
107
- }
108
-
109
- if (entry.catchupPolicy === "all") {
110
- const matches = this.findAllMatches(entry.cron, searchFrom, now, entry.id)
111
-
112
- if (matches.length === 0) return false
113
-
114
- for (const match of matches) {
115
- await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime())
116
- }
117
-
118
- state.set(entry.id, matches[matches.length - 1] ?? now)
119
- return true
120
- }
121
-
122
- const match = this.findMostRecentMatch(entry.cron, searchFrom, now, entry.id)
123
-
124
- if (!match) return false
125
-
126
- await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime())
127
- state.set(entry.id, match)
128
- return true
129
- }
130
-
131
- private async notifyOne(
132
- entry: ScheduleEntry,
133
- firedAt: Date,
134
- notify: NotifyFn,
135
- catchup: boolean,
136
- ): Promise<void> {
137
- const meta: Record<string, string> = {
138
- event_type: "schedule",
139
- schedule_id: entry.id,
140
- cron: entry.cron,
141
- fired_at: firedAt.toISOString(),
142
- catchup_policy: entry.catchupPolicy,
143
- }
144
-
145
- if (catchup) meta.catchup = "true"
146
-
147
- await notify(entry.prompt, meta)
148
- }
149
-
150
- private findMostRecentMatch(cron: string, from: Date, until: Date, entryId: string): Date | null {
151
- const maxIterations = Math.min(
152
- MAX_CATCHUP_MINUTES,
153
- Math.floor((until.getTime() - from.getTime()) / 60_000) + 1,
154
- )
155
-
156
- for (let i = 0; i < maxIterations; i++) {
157
- const candidate = new Date(until.getTime() - i * 60_000)
158
-
159
- try {
160
- if (matchCron(cron, candidate)) return candidate
161
- } catch (error) {
162
- this.logInvalidCron({ id: entryId, cron } as ScheduleEntry, error)
163
- return null
164
- }
165
- }
166
-
167
- return null
168
- }
169
-
170
- private findAllMatches(cron: string, from: Date, until: Date, entryId: string): Date[] {
171
- const maxIterations = Math.min(
172
- MAX_CATCHUP_MINUTES,
173
- Math.floor((until.getTime() - from.getTime()) / 60_000) + 1,
174
- )
175
- const matches: Date[] = []
176
-
177
- for (let i = 0; i < maxIterations; i++) {
178
- const candidate = new Date(from.getTime() + i * 60_000)
179
-
180
- if (candidate.getTime() > until.getTime()) break
181
-
182
- try {
183
- if (matchCron(cron, candidate)) matches.push(candidate)
184
- } catch (error) {
185
- this.logInvalidCron({ id: entryId, cron } as ScheduleEntry, error)
186
- return []
187
- }
188
- }
189
-
190
- return matches
191
- }
192
-
193
- private logInvalidCron(entry: Pick<ScheduleEntry, "id" | "cron">, error: unknown): void {
194
- this.logger.error("invalid cron expression in schedule", {
195
- connector: this.config.name,
196
- id: entry.id,
197
- cron: entry.cron,
198
- error: error instanceof Error ? error.message : String(error),
199
- })
200
- }
201
-
202
- private truncateToMinute(date: Date): Date {
203
- const copy = new Date(date.getTime())
204
- copy.setSeconds(0, 0)
205
- return copy
206
- }
207
- }
@@ -1,54 +0,0 @@
1
- import { dirname } from "node:path"
2
- import { FunnelFileSystem } from "@/engine/fs/file-system"
3
- import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
4
-
5
- type Deps = {
6
- path: string
7
- fs?: FunnelFileSystem
8
- }
9
-
10
- const defaultFs = new NodeFunnelFileSystem()
11
-
12
- /**
13
- * Per-connector lastFiredAt persistence for the schedule listener. The path is
14
- * passed in by FunnelConnectorFactory so this store does not know about the
15
- * funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
16
- * outside this class).
17
- */
18
- export class ScheduleStateStore {
19
- private readonly path: string
20
- private readonly fs: FunnelFileSystem
21
-
22
- constructor(deps: Deps) {
23
- this.path = deps.path
24
- this.fs = deps.fs ?? defaultFs
25
- Object.freeze(this)
26
- }
27
-
28
- load(): Map<string, Date> {
29
- const map = new Map<string, Date>()
30
-
31
- if (!this.fs.existsSync(this.path)) return map
32
-
33
- const raw: unknown = JSON.parse(this.fs.readFileSync(this.path))
34
-
35
- if (raw === null || typeof raw !== "object") return map
36
-
37
- for (const [id, iso] of Object.entries(raw)) {
38
- if (typeof iso === "string") map.set(id, new Date(iso))
39
- }
40
-
41
- return map
42
- }
43
-
44
- save(state: Map<string, Date>): void {
45
- const obj: Record<string, string> = {}
46
-
47
- for (const [id, date] of state) {
48
- obj[id] = date.toISOString()
49
- }
50
-
51
- this.fs.mkdirSync(dirname(this.path), { recursive: true })
52
- this.fs.writeFileSync(this.path, `${JSON.stringify(obj, null, 2)}\n`)
53
- }
54
- }
@@ -1,4 +0,0 @@
1
- export * from "@/connectors/match-cron"
2
- export * from "@/connectors/schedule-connector-schema"
3
- export * from "@/connectors/schedule-listener"
4
- export * from "@/connectors/schedule-state-store"
@@ -1,36 +0,0 @@
1
- import { WebClient } from "@slack/web-api"
2
- import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
3
- import type { SlackConnectorConfig } from "@/connectors/slack-connector-schema"
4
-
5
- export type SlackWebClientLike = {
6
- apiCall: (method: string, options?: Record<string, unknown>) => Promise<unknown>
7
- }
8
-
9
- const toRecord = (value: object): Record<string, unknown> => {
10
- const result: Record<string, unknown> = {}
11
-
12
- for (const [key, val] of Object.entries(value)) result[key] = val
13
-
14
- return result
15
- }
16
-
17
- type Deps = {
18
- config: SlackConnectorConfig
19
- client?: SlackWebClientLike
20
- }
21
-
22
- export class FunnelSlackAdapter extends FunnelConnectorAdapter {
23
- private readonly client: SlackWebClientLike
24
-
25
- constructor(deps: Deps) {
26
- super()
27
- this.client = deps.client ?? new WebClient(deps.config.botToken)
28
- Object.freeze(this)
29
- }
30
-
31
- async call(input: CallInput): Promise<unknown> {
32
- const body = input.body !== null && typeof input.body === "object" ? toRecord(input.body) : {}
33
-
34
- return await this.client.apiCall(input.path, body)
35
- }
36
- }
@@ -1,13 +0,0 @@
1
- import { z } from "zod"
2
-
3
- export const slackConnectorSchema = z.object({
4
- id: z.string(),
5
- name: z.string(),
6
- type: z.literal("slack"),
7
- botToken: z.string().startsWith("xoxb-"),
8
- appToken: z.string().startsWith("xapp-"),
9
- createdAt: z.string().datetime().optional(),
10
- updatedAt: z.string().datetime().optional(),
11
- })
12
-
13
- export type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>
@@ -1,97 +0,0 @@
1
- export type SlackRawEvent = Record<string, unknown>
2
-
3
- export type SlackProcessedSkip = { skip: true }
4
-
5
- export type SlackProcessedEmit = {
6
- skip: false
7
- content: string
8
- meta: Record<string, string>
9
- shouldReact: boolean
10
- channel: string
11
- timestamp: string
12
- }
13
-
14
- export type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit
15
-
16
- const ALLOWED_EVENTS = new Set(["message", "app_mention"])
17
- const ALLOWED_SUBTYPES = new Set<string | undefined>([
18
- undefined,
19
- "thread_broadcast",
20
- "bot_message",
21
- "file_share",
22
- ])
23
-
24
- const DEDUP_WINDOW = 10_000
25
-
26
- type Props = {
27
- ownBotUserId: string
28
- ownBotId: string
29
- now?: () => number
30
- }
31
-
32
- const getString = (event: SlackRawEvent, key: string): string | undefined => {
33
- const value = event[key]
34
-
35
- return typeof value === "string" ? value : undefined
36
- }
37
-
38
- export class FunnelSlackEventProcessor {
39
- private readonly ownBotUserId: string
40
- private readonly ownBotId: string
41
- private readonly now: () => number
42
- private readonly dedup = new Map<string, number>()
43
-
44
- constructor(props: Props) {
45
- this.ownBotUserId = props.ownBotUserId
46
- this.ownBotId = props.ownBotId
47
- this.now = props.now ?? (() => Date.now())
48
- }
49
-
50
- process(event: SlackRawEvent): SlackProcessed {
51
- const eventType = getString(event, "type")
52
-
53
- if (!eventType || !ALLOWED_EVENTS.has(eventType)) return { skip: true }
54
-
55
- const subtype = getString(event, "subtype")
56
-
57
- if (!ALLOWED_SUBTYPES.has(subtype)) return { skip: true }
58
-
59
- const channelId = getString(event, "channel") ?? ""
60
- const eventTs = getString(event, "event_ts") ?? getString(event, "ts") ?? ""
61
- const dedupKey = `${channelId}:${eventTs}`
62
- const now = this.now()
63
-
64
- if (this.dedup.has(dedupKey)) return { skip: true }
65
-
66
- this.dedup.set(dedupKey, now)
67
-
68
- for (const key of this.dedup.keys()) {
69
- if ((this.dedup.get(key) ?? 0) < now - DEDUP_WINDOW) this.dedup.delete(key)
70
- }
71
-
72
- const userId = getString(event, "user")
73
- const botId = getString(event, "bot_id")
74
-
75
- if (userId === this.ownBotUserId) return { skip: true }
76
- if (botId === this.ownBotId) return { skip: true }
77
-
78
- const text = getString(event, "text") ?? ""
79
- const mentioned = text.includes(`<@${this.ownBotUserId}>`)
80
- const threadTs = getString(event, "thread_ts") ?? getString(event, "ts") ?? ""
81
-
82
- return {
83
- skip: false,
84
- content: JSON.stringify(event),
85
- meta: {
86
- event_type: "slack",
87
- channel_id: channelId,
88
- user_id: userId ?? "",
89
- mentioned: String(mentioned),
90
- thread_ts: threadTs,
91
- },
92
- shouldReact: mentioned,
93
- channel: channelId,
94
- timestamp: getString(event, "ts") ?? "",
95
- }
96
- }
97
- }
@@ -1,97 +0,0 @@
1
- import { App, LogLevel } from "@slack/bolt"
2
- import { z } from "zod"
3
- import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
4
- import { FunnelSlackEventProcessor } from "@/connectors/slack-event-processor"
5
- import { FunnelLogger } from "@/engine/logger/logger"
6
- import { NodeFunnelLogger } from "@/engine/logger/node-logger"
7
- import type { SlackConnectorConfig } from "@/connectors/slack-connector-schema"
8
-
9
- const middlewareArgsSchema = z.object({
10
- event: z.record(z.string(), z.unknown()).optional(),
11
- })
12
-
13
- type Deps = {
14
- config: SlackConnectorConfig
15
- logger?: FunnelLogger
16
- }
17
-
18
- const defaultLogger = new NodeFunnelLogger()
19
-
20
- export class FunnelSlackListener extends FunnelConnectorListener {
21
- private readonly config: SlackConnectorConfig
22
- private readonly logger: FunnelLogger
23
- private app: App | null = null
24
-
25
- constructor(deps: Deps) {
26
- super()
27
- this.config = deps.config
28
- this.logger = deps.logger ?? defaultLogger
29
- }
30
-
31
- async start(notify: NotifyFn): Promise<void> {
32
- const app = new App({
33
- token: this.config.botToken,
34
- appToken: this.config.appToken,
35
- socketMode: true,
36
- logLevel: LogLevel.ERROR,
37
- })
38
-
39
- const authResult = await app.client.auth.test({ token: this.config.botToken })
40
- const processor = new FunnelSlackEventProcessor({
41
- ownBotUserId: authResult.user_id ?? "",
42
- ownBotId: authResult.bot_id ?? "",
43
- })
44
-
45
- app.use(async (args) => {
46
- const parsed = middlewareArgsSchema.safeParse(args)
47
-
48
- if (!parsed.success || !parsed.data.event) return
49
-
50
- const result = processor.process(parsed.data.event)
51
-
52
- if (result.skip) return
53
-
54
- if (result.shouldReact) {
55
- try {
56
- await app.client.reactions.add({
57
- token: this.config.botToken,
58
- channel: result.channel,
59
- timestamp: result.timestamp,
60
- name: "eyes",
61
- })
62
- } catch {
63
- // ignore
64
- }
65
- }
66
-
67
- await notify(result.content, result.meta)
68
- })
69
-
70
- app.error(async (error) => {
71
- this.logger.error("Slack error", {
72
- error: error instanceof Error ? error.message : String(error),
73
- })
74
- })
75
-
76
- await app.start()
77
- this.app = app
78
- }
79
-
80
- async stop(): Promise<void> {
81
- if (!this.app) return
82
-
83
- try {
84
- await this.app.stop()
85
- } catch (error) {
86
- this.logger.error("Slack stop error", {
87
- error: error instanceof Error ? error.message : String(error),
88
- })
89
- } finally {
90
- this.app = null
91
- }
92
- }
93
-
94
- override isAlive(): boolean {
95
- return this.app !== null
96
- }
97
- }
@@ -1,4 +0,0 @@
1
- export * from "@/connectors/slack-adapter"
2
- export * from "@/connectors/slack-connector-schema"
3
- export * from "@/connectors/slack-event-processor"
4
- export * from "@/connectors/slack-listener"