@interactive-inc/claude-funnel 0.8.1 → 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.
- package/README.md +179 -80
- package/dist/bin.js +726 -658
- package/dist/connector-adapter-CXB-q_XC.d.ts +11 -0
- package/dist/connector-adapter-D5Utumgz.js +4 -0
- package/dist/connectors/discord.d.ts +76 -0
- package/dist/connectors/discord.js +2 -0
- package/dist/connectors/gh.d.ts +38 -0
- package/dist/connectors/gh.js +2 -0
- package/dist/connectors/schedule.d.ts +53 -0
- package/dist/connectors/schedule.js +2 -0
- package/dist/connectors/slack.d.ts +34 -0
- package/dist/connectors/slack.js +2 -0
- package/dist/discord-connector-schema-Dww2I4zH.d.ts +14 -0
- package/dist/discord-connector-schema-ygf5Df-2.js +173 -0
- package/dist/file-system-Co60LrmR.d.ts +74 -0
- package/dist/gateway/daemon.js +233 -183
- package/dist/gh-connector-schema-2ml29MBC.js +218 -0
- package/dist/gh-connector-schema-BZFAS-p-.d.ts +45 -0
- package/dist/index.d.ts +3881 -36
- package/dist/index.js +6217 -3483
- package/dist/logger-CTlXs7z4.d.ts +33 -0
- package/dist/node-logger-DQz_BGOD.js +61 -0
- package/dist/schedule-connector-schema-CkuIQ0JQ.js +325 -0
- package/dist/slack-connector-schema-Cd22WiHB.js +153 -0
- package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
- package/package.json +34 -28
- package/dist/cli/factory.d.ts +0 -7
- package/dist/cli/router/query-to-cli-args.d.ts +0 -1
- package/dist/cli/router/to-request.d.ts +0 -5
- package/dist/cli/router/validator.d.ts +0 -5
- package/dist/cli/routes/channels.$channel.connectors.$connector.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.$connector.rename.$newName.d.ts +0 -46
- package/dist/cli/routes/channels.$channel.connectors.$connector.request.d.ts +0 -54
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.d.ts +0 -66
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.d.ts +0 -46
- package/dist/cli/routes/channels.$channel.connectors.add.$connector.d.ts +0 -90
- package/dist/cli/routes/channels.$channel.connectors.d.ts +0 -38
- package/dist/cli/routes/channels.$channel.connectors.remove.$connector.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.connectors.set.$connector.d.ts +0 -62
- package/dist/cli/routes/channels.$channel.d.ts +0 -38
- package/dist/cli/routes/channels.$channel.rename.$newName.d.ts +0 -42
- package/dist/cli/routes/channels.$channel.set.delivery.$mode.d.ts +0 -28
- package/dist/cli/routes/channels.add.$channel.d.ts +0 -46
- package/dist/cli/routes/channels.d.ts +0 -16
- package/dist/cli/routes/channels.remove.$channel.d.ts +0 -38
- package/dist/cli/routes/claude.d.ts +0 -32
- package/dist/cli/routes/gateway.d.ts +0 -20
- package/dist/cli/routes/gateway.listeners.d.ts +0 -17
- package/dist/cli/routes/gateway.logs.d.ts +0 -24
- package/dist/cli/routes/gateway.restart.d.ts +0 -24
- package/dist/cli/routes/gateway.run.d.ts +0 -24
- package/dist/cli/routes/gateway.start.d.ts +0 -24
- package/dist/cli/routes/gateway.status.d.ts +0 -13
- package/dist/cli/routes/gateway.stop.d.ts +0 -16
- package/dist/cli/routes/index.d.ts +0 -1222
- package/dist/cli/routes/profiles.$profile.as-default.d.ts +0 -38
- package/dist/cli/routes/profiles.$profile.rename.$newName.d.ts +0 -42
- package/dist/cli/routes/profiles.$profile.run.d.ts +0 -46
- package/dist/cli/routes/profiles.add.$profile.d.ts +0 -54
- package/dist/cli/routes/profiles.d.ts +0 -16
- package/dist/cli/routes/profiles.remove.$profile.d.ts +0 -38
- package/dist/cli/routes/profiles.set.$profile.d.ts +0 -54
- package/dist/cli/routes/status.d.ts +0 -16
- package/dist/cli/routes/update.d.ts +0 -16
- package/dist/connectors/connector-adapter.d.ts +0 -8
- package/dist/connectors/connector-config-schema.d.ts +0 -43
- package/dist/connectors/connector-factory.d.ts +0 -32
- package/dist/connectors/connector-listener.d.ts +0 -17
- package/dist/connectors/discord-adapter.d.ts +0 -14
- package/dist/connectors/discord-connector-schema.d.ts +0 -10
- package/dist/connectors/discord-event-processor.d.ts +0 -26
- package/dist/connectors/discord-listener.d.ts +0 -17
- package/dist/connectors/gh-adapter.d.ts +0 -11
- package/dist/connectors/gh-connector-schema.d.ts +0 -10
- package/dist/connectors/gh-listener.d.ts +0 -26
- package/dist/connectors/match-cron.d.ts +0 -1
- package/dist/connectors/schedule-connector-schema.d.ts +0 -45
- package/dist/connectors/schedule-listener.d.ts +0 -30
- package/dist/connectors/schedule-state-store.d.ts +0 -19
- package/dist/connectors/slack-adapter.d.ts +0 -15
- package/dist/connectors/slack-connector-schema.d.ts +0 -11
- package/dist/connectors/slack-event-processor.d.ts +0 -27
- package/dist/connectors/slack-listener.d.ts +0 -17
- package/dist/engine/channels/channels.d.ts +0 -106
- package/dist/engine/claude/claude.d.ts +0 -49
- package/dist/engine/claude/gateway-controller.d.ts +0 -6
- package/dist/engine/fs/file-system.d.ts +0 -24
- package/dist/engine/fs/memory-file-system.d.ts +0 -31
- package/dist/engine/fs/node-file-system.d.ts +0 -15
- package/dist/engine/http/http-client.d.ts +0 -15
- package/dist/engine/http/memory-http-client.d.ts +0 -12
- package/dist/engine/http/node-http-client.d.ts +0 -5
- package/dist/engine/id/id-generator.d.ts +0 -7
- package/dist/engine/id/memory-id-generator.d.ts +0 -11
- package/dist/engine/id/node-id-generator.d.ts +0 -4
- package/dist/engine/logger/logger.d.ts +0 -11
- package/dist/engine/logger/memory-logger.d.ts +0 -14
- package/dist/engine/logger/node-logger.d.ts +0 -15
- package/dist/engine/logger/noop-logger.d.ts +0 -7
- package/dist/engine/mcp/channel-server.d.ts +0 -1
- package/dist/engine/mcp/mcp.d.ts +0 -22
- package/dist/engine/process/memory-process-runner.d.ts +0 -43
- package/dist/engine/process/node-process-runner.d.ts +0 -9
- package/dist/engine/process/process-runner.d.ts +0 -29
- package/dist/engine/profiles/profile-channel-checker.d.ts +0 -7
- package/dist/engine/profiles/profiles.d.ts +0 -31
- package/dist/engine/settings/mock-settings-reader.d.ts +0 -9
- package/dist/engine/settings/settings-reader.d.ts +0 -5
- package/dist/engine/settings/settings-schema.d.ts +0 -132
- package/dist/engine/settings/settings-store.d.ts +0 -18
- package/dist/engine/time/clock.d.ts +0 -9
- package/dist/engine/time/memory-clock.d.ts +0 -12
- package/dist/engine/time/node-clock.d.ts +0 -4
- package/dist/funnel.d.ts +0 -95
- package/dist/gateway/auth-middleware.d.ts +0 -14
- package/dist/gateway/broadcaster.d.ts +0 -122
- package/dist/gateway/daemon.d.ts +0 -2
- package/dist/gateway/factory.d.ts +0 -7
- package/dist/gateway/funnel-event-store.d.ts +0 -81
- package/dist/gateway/gateway-server.d.ts +0 -94
- package/dist/gateway/gateway-token.d.ts +0 -33
- package/dist/gateway/gateway.d.ts +0 -58
- package/dist/gateway/kill-competing-slack-gateways.d.ts +0 -9
- package/dist/gateway/listener-supervisor.d.ts +0 -85
- package/dist/gateway/listeners-client.d.ts +0 -53
- package/dist/gateway/resolve-daemon-script.d.ts +0 -11
- package/dist/gateway/routes/channels.connectors.call.d.ts +0 -41
- package/dist/gateway/routes/health.d.ts +0 -17
- package/dist/gateway/routes/index.d.ts +0 -209
- package/dist/gateway/routes/listeners.list.d.ts +0 -14
- package/dist/gateway/routes/listeners.restart.d.ts +0 -34
- package/dist/gateway/routes/listeners.start.d.ts +0 -34
- package/dist/gateway/routes/listeners.stop.d.ts +0 -34
- package/dist/gateway/routes/route-deps.d.ts +0 -10
- package/dist/gateway/routes/status.d.ts +0 -30
- package/dist/gateway/routes/validator.d.ts +0 -19
- package/dist/logger/leuco-human-file-writer.d.ts +0 -33
- package/dist/logger/leuco-human-logger.d.ts +0 -46
- package/dist/logger/leuco-human-record.d.ts +0 -15
- package/dist/logger/leuco-human-stdout-writer.d.ts +0 -20
- package/dist/logger/leuco-human-writer.d.ts +0 -13
- package/dist/logger/leuco-logger-memory-sink.d.ts +0 -33
- package/dist/logger/leuco-logger-record.d.ts +0 -13
- package/dist/logger/leuco-logger-sink.d.ts +0 -34
- package/dist/logger/leuco-logger-sqlite-sink.d.ts +0 -102
- package/dist/logger/leuco-logger.d.ts +0 -56
- package/lib/bin.ts +0 -78
- package/lib/cli/factory.ts +0 -10
- package/lib/cli/router/query-to-cli-args.ts +0 -20
- package/lib/cli/router/to-request.ts +0 -112
- package/lib/cli/router/validator.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.rename.$newName.ts +0 -27
- package/lib/cli/routes/channels.$channel.connectors.$connector.request.ts +0 -40
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.ts +0 -41
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.$connector.schedules.ts +0 -23
- package/lib/cli/routes/channels.$channel.connectors.$connector.ts +0 -26
- package/lib/cli/routes/channels.$channel.connectors.add.$connector.ts +0 -92
- package/lib/cli/routes/channels.$channel.connectors.remove.$connector.ts +0 -22
- package/lib/cli/routes/channels.$channel.connectors.set.$connector.ts +0 -63
- package/lib/cli/routes/channels.$channel.connectors.ts +0 -26
- package/lib/cli/routes/channels.$channel.rename.$newName.ts +0 -22
- package/lib/cli/routes/channels.$channel.set.delivery.$mode.ts +0 -34
- package/lib/cli/routes/channels.$channel.ts +0 -34
- package/lib/cli/routes/channels.add.$channel.ts +0 -33
- package/lib/cli/routes/channels.remove.$channel.ts +0 -20
- package/lib/cli/routes/channels.ts +0 -39
- package/lib/cli/routes/claude.ts +0 -69
- package/lib/cli/routes/gateway.listeners.ts +0 -41
- package/lib/cli/routes/gateway.logs.ts +0 -123
- package/lib/cli/routes/gateway.restart.ts +0 -50
- package/lib/cli/routes/gateway.run.ts +0 -41
- package/lib/cli/routes/gateway.start.ts +0 -50
- package/lib/cli/routes/gateway.status.ts +0 -19
- package/lib/cli/routes/gateway.stop.ts +0 -32
- package/lib/cli/routes/gateway.ts +0 -55
- package/lib/cli/routes/index.ts +0 -202
- package/lib/cli/routes/profiles.$profile.as-default.ts +0 -22
- package/lib/cli/routes/profiles.$profile.rename.$newName.ts +0 -22
- package/lib/cli/routes/profiles.$profile.run.ts +0 -36
- package/lib/cli/routes/profiles.add.$profile.ts +0 -46
- package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
- package/lib/cli/routes/profiles.set.$profile.ts +0 -46
- package/lib/cli/routes/profiles.ts +0 -40
- package/lib/cli/routes/status.ts +0 -93
- package/lib/cli/routes/update.ts +0 -27
- package/lib/connectors/connector-adapter.ts +0 -9
- package/lib/connectors/connector-config-schema.ts +0 -16
- package/lib/connectors/connector-factory.ts +0 -94
- package/lib/connectors/connector-listener.ts +0 -20
- package/lib/connectors/discord-adapter.ts +0 -51
- package/lib/connectors/discord-connector-schema.ts +0 -12
- package/lib/connectors/discord-event-processor.ts +0 -48
- package/lib/connectors/discord-listener.ts +0 -111
- package/lib/connectors/gh-adapter.ts +0 -48
- package/lib/connectors/gh-connector-schema.ts +0 -12
- package/lib/connectors/gh-listener.ts +0 -137
- package/lib/connectors/match-cron.ts +0 -78
- package/lib/connectors/schedule-connector-schema.ts +0 -33
- package/lib/connectors/schedule-listener.ts +0 -207
- package/lib/connectors/schedule-state-store.ts +0 -54
- package/lib/connectors/slack-adapter.ts +0 -36
- package/lib/connectors/slack-connector-schema.ts +0 -13
- package/lib/connectors/slack-event-processor.ts +0 -97
- package/lib/connectors/slack-listener.ts +0 -97
- package/lib/engine/channels/channels.ts +0 -520
- package/lib/engine/claude/claude.ts +0 -199
- package/lib/engine/claude/gateway-controller.ts +0 -4
- package/lib/engine/fs/file-system.ts +0 -23
- package/lib/engine/fs/memory-file-system.ts +0 -102
- package/lib/engine/fs/node-file-system.ts +0 -68
- package/lib/engine/http/http-client.ts +0 -17
- package/lib/engine/http/memory-http-client.ts +0 -36
- package/lib/engine/http/node-http-client.ts +0 -23
- package/lib/engine/id/id-generator.ts +0 -7
- package/lib/engine/id/memory-id-generator.ts +0 -20
- package/lib/engine/id/node-id-generator.ts +0 -7
- package/lib/engine/logger/logger.ts +0 -11
- package/lib/engine/logger/memory-logger.ts +0 -28
- package/lib/engine/logger/node-logger.ts +0 -49
- package/lib/engine/logger/noop-logger.ts +0 -9
- package/lib/engine/mcp/channel-server.ts +0 -204
- package/lib/engine/mcp/mcp.ts +0 -126
- package/lib/engine/process/memory-process-runner.ts +0 -88
- package/lib/engine/process/node-process-runner.ts +0 -91
- package/lib/engine/process/process-runner.ts +0 -33
- package/lib/engine/profiles/profile-channel-checker.ts +0 -7
- package/lib/engine/profiles/profiles.ts +0 -126
- package/lib/engine/settings/mock-settings-reader.ts +0 -27
- package/lib/engine/settings/settings-reader.ts +0 -6
- package/lib/engine/settings/settings-schema.ts +0 -46
- package/lib/engine/settings/settings-store.ts +0 -110
- package/lib/engine/time/clock.ts +0 -15
- package/lib/engine/time/memory-clock.ts +0 -26
- package/lib/engine/time/node-clock.ts +0 -7
- package/lib/funnel.ts +0 -187
- package/lib/gateway/auth-middleware.ts +0 -44
- package/lib/gateway/broadcaster.ts +0 -319
- package/lib/gateway/daemon.ts +0 -47
- package/lib/gateway/factory.ts +0 -10
- package/lib/gateway/funnel-event-store.ts +0 -155
- package/lib/gateway/gateway-server.ts +0 -414
- package/lib/gateway/gateway-token.ts +0 -79
- package/lib/gateway/gateway.ts +0 -209
- package/lib/gateway/kill-competing-slack-gateways.ts +0 -56
- package/lib/gateway/listener-supervisor.ts +0 -339
- package/lib/gateway/listeners-client.ts +0 -128
- package/lib/gateway/resolve-daemon-script.ts +0 -26
- package/lib/gateway/routes/channels.connectors.call.ts +0 -39
- package/lib/gateway/routes/health.ts +0 -13
- package/lib/gateway/routes/index.ts +0 -24
- package/lib/gateway/routes/listeners.list.ts +0 -6
- package/lib/gateway/routes/listeners.restart.ts +0 -15
- package/lib/gateway/routes/listeners.start.ts +0 -15
- package/lib/gateway/routes/listeners.stop.ts +0 -15
- package/lib/gateway/routes/route-deps.ts +0 -11
- package/lib/gateway/routes/status.ts +0 -15
- package/lib/gateway/routes/validator.ts +0 -17
- package/lib/index.ts +0 -52
- package/lib/logger/leuco-human-file-writer.ts +0 -65
- package/lib/logger/leuco-human-logger.ts +0 -98
- package/lib/logger/leuco-human-record.ts +0 -16
- package/lib/logger/leuco-human-stdout-writer.ts +0 -26
- package/lib/logger/leuco-human-writer.ts +0 -14
- package/lib/logger/leuco-logger-memory-sink.ts +0 -67
- package/lib/logger/leuco-logger-record.ts +0 -13
- package/lib/logger/leuco-logger-sink.ts +0 -33
- package/lib/logger/leuco-logger-sqlite-sink.ts +0 -355
- package/lib/logger/leuco-logger.ts +0 -135
- package/lib/tui/app.tsx +0 -357
- package/lib/tui/components/add-row.tsx +0 -18
- package/lib/tui/components/brand.tsx +0 -27
- package/lib/tui/components/card.tsx +0 -44
- package/lib/tui/components/detail-bar.tsx +0 -46
- package/lib/tui/components/editable-field.tsx +0 -33
- package/lib/tui/components/empty-state.tsx +0 -11
- package/lib/tui/components/gateway-status.tsx +0 -66
- package/lib/tui/components/keymap.tsx +0 -29
- package/lib/tui/components/menu-item.tsx +0 -73
- package/lib/tui/components/menu.tsx +0 -26
- package/lib/tui/components/panel-header.tsx +0 -22
- package/lib/tui/components/readonly-field.tsx +0 -18
- package/lib/tui/components/section-header.tsx +0 -25
- package/lib/tui/components/selection-accent.tsx +0 -32
- package/lib/tui/components/session-item.tsx +0 -33
- package/lib/tui/components/session-list.tsx +0 -33
- package/lib/tui/components/ui/hascii/accordion-item.tsx +0 -88
- package/lib/tui/components/ui/hascii/accordion.tsx +0 -96
- package/lib/tui/components/ui/hascii/alert-dialog.tsx +0 -43
- package/lib/tui/components/ui/hascii/badge.tsx +0 -51
- package/lib/tui/components/ui/hascii/breadcrumb.tsx +0 -58
- package/lib/tui/components/ui/hascii/button.tsx +0 -194
- package/lib/tui/components/ui/hascii/card-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/card-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/card-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/card.tsx +0 -27
- package/lib/tui/components/ui/hascii/checkbox.tsx +0 -65
- package/lib/tui/components/ui/hascii/command.tsx +0 -159
- package/lib/tui/components/ui/hascii/dialog-content.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-description.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog-footer.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/dialog-title.tsx +0 -13
- package/lib/tui/components/ui/hascii/dialog.tsx +0 -27
- package/lib/tui/components/ui/hascii/file-tree.tsx +0 -142
- package/lib/tui/components/ui/hascii/focus-group.tsx +0 -62
- package/lib/tui/components/ui/hascii/form-item.tsx +0 -43
- package/lib/tui/components/ui/hascii/input-otp.tsx +0 -86
- package/lib/tui/components/ui/hascii/input.tsx +0 -130
- package/lib/tui/components/ui/hascii/pagination.tsx +0 -105
- package/lib/tui/components/ui/hascii/progress.tsx +0 -28
- package/lib/tui/components/ui/hascii/select.tsx +0 -131
- package/lib/tui/components/ui/hascii/separator.tsx +0 -35
- package/lib/tui/components/ui/hascii/sidebar-content.tsx +0 -23
- package/lib/tui/components/ui/hascii/sidebar-header.tsx +0 -14
- package/lib/tui/components/ui/hascii/sidebar-menu-item.tsx +0 -67
- package/lib/tui/components/ui/hascii/sidebar.tsx +0 -24
- package/lib/tui/components/ui/hascii/skeleton.tsx +0 -60
- package/lib/tui/components/ui/hascii/slider.tsx +0 -91
- package/lib/tui/components/ui/hascii/snackbar.tsx +0 -75
- package/lib/tui/components/ui/hascii/sparkline.tsx +0 -53
- package/lib/tui/components/ui/hascii/spinner.tsx +0 -47
- package/lib/tui/components/ui/hascii/stepper.tsx +0 -54
- package/lib/tui/components/ui/hascii/switch.tsx +0 -66
- package/lib/tui/components/ui/hascii/table.tsx +0 -95
- package/lib/tui/components/ui/hascii/tabs.tsx +0 -59
- package/lib/tui/components/ui/hascii/toggle-group-item.tsx +0 -45
- package/lib/tui/components/ui/hascii/toggle-group.tsx +0 -99
- package/lib/tui/components/ui/hascii/tree.tsx +0 -104
- package/lib/tui/components/view-shell.tsx +0 -44
- package/lib/tui/filter-input.tsx +0 -33
- package/lib/tui/hooks/hascii/use-pressable.ts +0 -54
- package/lib/tui/parse-comma-list.ts +0 -14
- package/lib/tui/profile-launcher.tsx +0 -61
- package/lib/tui/scrollbar-options.ts +0 -19
- package/lib/tui/sidebar.tsx +0 -50
- package/lib/tui/theme.ts +0 -40
- package/lib/tui/tui.tsx +0 -20
- package/lib/tui/types.ts +0 -38
- package/lib/tui/unique-name.ts +0 -18
- package/lib/tui/use-event-stream.ts +0 -133
- package/lib/tui/use-snapshot.ts +0 -99
- package/lib/tui/utils/hascii/form-item-context.tsx +0 -23
- package/lib/tui/utils/hascii/input-focus-context.tsx +0 -31
- package/lib/tui/utils/hascii/theme-context.tsx +0 -26
- package/lib/tui/utils/hascii/theme.ts +0 -176
- package/lib/tui/views/channels-view.tsx +0 -108
- package/lib/tui/views/connectors-view.tsx +0 -164
- package/lib/tui/views/events-view.tsx +0 -160
- package/lib/tui/views/listeners-view.tsx +0 -80
- package/lib/tui/views/profiles-view.tsx +0 -152
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region lib/connectors/connector-listener.d.ts
|
|
2
|
+
type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Long-lived event source for one connector.
|
|
5
|
+
*
|
|
6
|
+
* `start()` opens the underlying connection (Slack Socket Mode, Discord
|
|
7
|
+
* Gateway, GH polling, schedule tick) and pushes events through `notify`.
|
|
8
|
+
* `stop()` releases the resources so the supervisor can recreate the listener
|
|
9
|
+
* with new config without restarting the whole gateway. `isAlive()` lets the
|
|
10
|
+
* supervisor periodically health-check and auto-restart dead listeners; the
|
|
11
|
+
* default optimistic implementation is fine for poll/tick-based listeners
|
|
12
|
+
* that self-heal.
|
|
13
|
+
*/
|
|
14
|
+
declare abstract class FunnelConnectorListener {
|
|
15
|
+
abstract start(notify: NotifyFn): Promise<void>;
|
|
16
|
+
abstract stop(): Promise<void>;
|
|
17
|
+
isAlive(): boolean;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region lib/engine/logger/logger.d.ts
|
|
21
|
+
/**
|
|
22
|
+
* Structured logger with three levels and an optional log-file path.
|
|
23
|
+
* Defaults to NodeFunnelLogger (appends to /tmp/funnel/funnel.log);
|
|
24
|
+
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
25
|
+
*/
|
|
26
|
+
declare abstract class FunnelLogger {
|
|
27
|
+
abstract info(message: string, meta?: Record<string, unknown>): void;
|
|
28
|
+
abstract warn(message: string, meta?: Record<string, unknown>): void;
|
|
29
|
+
abstract error(message: string, meta?: Record<string, unknown>): void;
|
|
30
|
+
abstract readonly file: string | null;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FunnelConnectorListener as n, NotifyFn as r, FunnelLogger as t };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
//#region lib/connectors/connector-listener.ts
|
|
4
|
+
/**
|
|
5
|
+
* Long-lived event source for one connector.
|
|
6
|
+
*
|
|
7
|
+
* `start()` opens the underlying connection (Slack Socket Mode, Discord
|
|
8
|
+
* Gateway, GH polling, schedule tick) and pushes events through `notify`.
|
|
9
|
+
* `stop()` releases the resources so the supervisor can recreate the listener
|
|
10
|
+
* with new config without restarting the whole gateway. `isAlive()` lets the
|
|
11
|
+
* supervisor periodically health-check and auto-restart dead listeners; the
|
|
12
|
+
* default optimistic implementation is fine for poll/tick-based listeners
|
|
13
|
+
* that self-heal.
|
|
14
|
+
*/
|
|
15
|
+
var FunnelConnectorListener = class {
|
|
16
|
+
isAlive() {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region lib/engine/logger/logger.ts
|
|
22
|
+
/**
|
|
23
|
+
* Structured logger with three levels and an optional log-file path.
|
|
24
|
+
* Defaults to NodeFunnelLogger (appends to /tmp/funnel/funnel.log);
|
|
25
|
+
* MemoryFunnelLogger captures entries in memory and NoopFunnelLogger silences output.
|
|
26
|
+
*/
|
|
27
|
+
var FunnelLogger = class {};
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region lib/engine/logger/node-logger.ts
|
|
30
|
+
const DEFAULT_LOG_FILE = join("/tmp/funnel", "funnel.log");
|
|
31
|
+
var NodeFunnelLogger = class extends FunnelLogger {
|
|
32
|
+
file;
|
|
33
|
+
now;
|
|
34
|
+
constructor(props = {}) {
|
|
35
|
+
super();
|
|
36
|
+
this.file = props.file ?? DEFAULT_LOG_FILE;
|
|
37
|
+
this.now = props.now ?? (() => /* @__PURE__ */ new Date());
|
|
38
|
+
Object.freeze(this);
|
|
39
|
+
}
|
|
40
|
+
info(message, meta) {
|
|
41
|
+
this.write("info", message, meta);
|
|
42
|
+
}
|
|
43
|
+
warn(message, meta) {
|
|
44
|
+
this.write("warn", message, meta);
|
|
45
|
+
}
|
|
46
|
+
error(message, meta) {
|
|
47
|
+
this.write("error", message, meta);
|
|
48
|
+
}
|
|
49
|
+
write(level, message, meta) {
|
|
50
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
51
|
+
const entry = {
|
|
52
|
+
time: this.now().toISOString(),
|
|
53
|
+
level,
|
|
54
|
+
message,
|
|
55
|
+
...meta ? { meta } : {}
|
|
56
|
+
};
|
|
57
|
+
appendFileSync(this.file, `${JSON.stringify(entry)}\n`);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { FunnelLogger as n, FunnelConnectorListener as r, NodeFunnelLogger as t };
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { r as FunnelConnectorListener, t as NodeFunnelLogger } from "./node-logger-DQz_BGOD.js";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region lib/connectors/match-cron.ts
|
|
6
|
+
const parseField = (expr, min, max) => {
|
|
7
|
+
const values = /* @__PURE__ */ new Set();
|
|
8
|
+
for (const part of expr.split(",")) {
|
|
9
|
+
const [rangePart, stepPart] = part.split("/");
|
|
10
|
+
const step = stepPart ? Number(stepPart) : 1;
|
|
11
|
+
if (!Number.isFinite(step) || step <= 0) throw new Error(`invalid cron step: "${stepPart}"`);
|
|
12
|
+
let lo = min;
|
|
13
|
+
let hi = max;
|
|
14
|
+
if (rangePart === "*" || rangePart === void 0 || rangePart === "") {
|
|
15
|
+
lo = min;
|
|
16
|
+
hi = max;
|
|
17
|
+
} else if (rangePart.includes("-")) {
|
|
18
|
+
const [aStr, bStr] = rangePart.split("-");
|
|
19
|
+
const a = Number(aStr);
|
|
20
|
+
const b = Number(bStr);
|
|
21
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) throw new Error(`invalid cron range: "${rangePart}"`);
|
|
22
|
+
lo = a;
|
|
23
|
+
hi = b;
|
|
24
|
+
} else {
|
|
25
|
+
const n = Number(rangePart);
|
|
26
|
+
if (!Number.isFinite(n)) throw new Error(`invalid cron value: "${rangePart}"`);
|
|
27
|
+
lo = n;
|
|
28
|
+
hi = stepPart ? max : n;
|
|
29
|
+
}
|
|
30
|
+
if (lo < min || hi > max || lo > hi) throw new Error(`cron value out of range: ${rangePart} (must be ${min}-${max})`);
|
|
31
|
+
for (let i = lo; i <= hi; i += step) values.add(i);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
min,
|
|
35
|
+
max,
|
|
36
|
+
values
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const matchCron = (expr, date) => {
|
|
40
|
+
const parts = expr.trim().split(/\s+/);
|
|
41
|
+
if (parts.length !== 5) throw new Error(`cron must have 5 fields (got ${parts.length}): "${expr}"`);
|
|
42
|
+
const [minute, hour, dom, month, dow] = parts;
|
|
43
|
+
if (!minute || !hour || !dom || !month || !dow) throw new Error(`cron has empty fields: "${expr}"`);
|
|
44
|
+
const fields = [
|
|
45
|
+
{
|
|
46
|
+
field: parseField(minute, 0, 59),
|
|
47
|
+
value: date.getMinutes()
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
field: parseField(hour, 0, 23),
|
|
51
|
+
value: date.getHours()
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
field: parseField(dom, 1, 31),
|
|
55
|
+
value: date.getDate()
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
field: parseField(month, 1, 12),
|
|
59
|
+
value: date.getMonth() + 1
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
field: parseField(dow, 0, 6),
|
|
63
|
+
value: date.getDay()
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
for (const { field, value } of fields) if (!field.values.has(value)) return false;
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region lib/engine/fs/file-system.ts
|
|
71
|
+
/**
|
|
72
|
+
* Filesystem boundary used everywhere funnel reads or writes.
|
|
73
|
+
* Default is NodeFunnelFileSystem (real `node:fs`); MemoryFunnelFileSystem
|
|
74
|
+
* provides a sandbox for tests and embedded use.
|
|
75
|
+
*/
|
|
76
|
+
var FunnelFileSystem = class {};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region lib/engine/fs/node-file-system.ts
|
|
79
|
+
const SECRET_MODE = 384;
|
|
80
|
+
var NodeFunnelFileSystem = class extends FunnelFileSystem {
|
|
81
|
+
constructor() {
|
|
82
|
+
super();
|
|
83
|
+
Object.freeze(this);
|
|
84
|
+
}
|
|
85
|
+
existsSync(path) {
|
|
86
|
+
return existsSync(path);
|
|
87
|
+
}
|
|
88
|
+
readFileSync(path) {
|
|
89
|
+
return readFileSync(path, "utf-8");
|
|
90
|
+
}
|
|
91
|
+
writeFileSync(path, data) {
|
|
92
|
+
writeFileSync(path, data);
|
|
93
|
+
}
|
|
94
|
+
writeSecretFileSync(path, data) {
|
|
95
|
+
writeFileSync(path, data, { mode: SECRET_MODE });
|
|
96
|
+
try {
|
|
97
|
+
chmodSync(path, SECRET_MODE);
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
appendFileSync(path, data) {
|
|
101
|
+
appendFileSync(path, data);
|
|
102
|
+
}
|
|
103
|
+
unlink(path) {
|
|
104
|
+
try {
|
|
105
|
+
unlinkSync(path);
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
mkdirSync(path, options) {
|
|
109
|
+
mkdirSync(path, { recursive: options?.recursive ?? false });
|
|
110
|
+
}
|
|
111
|
+
readdirSync(path) {
|
|
112
|
+
return readdirSync(path);
|
|
113
|
+
}
|
|
114
|
+
statSync(path) {
|
|
115
|
+
const stat = statSync(path);
|
|
116
|
+
return {
|
|
117
|
+
mtimeMs: stat.mtimeMs,
|
|
118
|
+
mode: stat.mode & 511
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region lib/connectors/schedule-state-store.ts
|
|
124
|
+
const defaultFs = new NodeFunnelFileSystem();
|
|
125
|
+
/**
|
|
126
|
+
* Per-connector lastFiredAt persistence for the schedule listener. The path is
|
|
127
|
+
* passed in by FunnelConnectorFactory so this store does not know about the
|
|
128
|
+
* funnel directory layout (`channels/<id>/connectors/<id>/state.json` lives
|
|
129
|
+
* outside this class).
|
|
130
|
+
*/
|
|
131
|
+
var ScheduleStateStore = class {
|
|
132
|
+
path;
|
|
133
|
+
fs;
|
|
134
|
+
constructor(deps) {
|
|
135
|
+
this.path = deps.path;
|
|
136
|
+
this.fs = deps.fs ?? defaultFs;
|
|
137
|
+
Object.freeze(this);
|
|
138
|
+
}
|
|
139
|
+
load() {
|
|
140
|
+
const map = /* @__PURE__ */ new Map();
|
|
141
|
+
if (!this.fs.existsSync(this.path)) return map;
|
|
142
|
+
const raw = JSON.parse(this.fs.readFileSync(this.path));
|
|
143
|
+
if (raw === null || typeof raw !== "object") return map;
|
|
144
|
+
for (const [id, iso] of Object.entries(raw)) if (typeof iso === "string") map.set(id, new Date(iso));
|
|
145
|
+
return map;
|
|
146
|
+
}
|
|
147
|
+
save(state) {
|
|
148
|
+
const obj = {};
|
|
149
|
+
for (const [id, date] of state) obj[id] = date.toISOString();
|
|
150
|
+
this.fs.mkdirSync(dirname(this.path), { recursive: true });
|
|
151
|
+
this.fs.writeFileSync(this.path, `${JSON.stringify(obj, null, 2)}\n`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region lib/connectors/schedule-listener.ts
|
|
156
|
+
const defaultLogger = new NodeFunnelLogger();
|
|
157
|
+
const MAX_CATCHUP_MINUTES = 1440;
|
|
158
|
+
var FunnelScheduleListener = class extends FunnelConnectorListener {
|
|
159
|
+
config;
|
|
160
|
+
lastFiredStore;
|
|
161
|
+
logger;
|
|
162
|
+
now;
|
|
163
|
+
timer = null;
|
|
164
|
+
stopped = false;
|
|
165
|
+
constructor(deps) {
|
|
166
|
+
super();
|
|
167
|
+
this.config = deps.config;
|
|
168
|
+
this.lastFiredStore = deps.lastFiredStore;
|
|
169
|
+
this.logger = deps.logger ?? defaultLogger;
|
|
170
|
+
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
171
|
+
}
|
|
172
|
+
async start(notify) {
|
|
173
|
+
this.stopped = false;
|
|
174
|
+
const scheduleNext = () => {
|
|
175
|
+
if (this.stopped) return;
|
|
176
|
+
const date = this.now();
|
|
177
|
+
const msUntilNextMinute = 6e4 - (date.getSeconds() * 1e3 + date.getMilliseconds());
|
|
178
|
+
this.timer = setTimeout(async () => {
|
|
179
|
+
if (this.stopped) return;
|
|
180
|
+
await this.tick(notify);
|
|
181
|
+
scheduleNext();
|
|
182
|
+
}, msUntilNextMinute);
|
|
183
|
+
this.timer.unref();
|
|
184
|
+
};
|
|
185
|
+
await this.tick(notify);
|
|
186
|
+
scheduleNext();
|
|
187
|
+
}
|
|
188
|
+
async stop() {
|
|
189
|
+
this.stopped = true;
|
|
190
|
+
if (this.timer) {
|
|
191
|
+
clearTimeout(this.timer);
|
|
192
|
+
this.timer = null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
isAlive() {
|
|
196
|
+
return !this.stopped && this.timer !== null;
|
|
197
|
+
}
|
|
198
|
+
async tick(notify) {
|
|
199
|
+
const now = this.truncateToMinute(this.now());
|
|
200
|
+
const state = this.lastFiredStore.load();
|
|
201
|
+
let changed = false;
|
|
202
|
+
for (const entry of this.config.entries) {
|
|
203
|
+
if (!entry.enabled) continue;
|
|
204
|
+
if (await this.fireEntry(entry, now, state, notify)) changed = true;
|
|
205
|
+
}
|
|
206
|
+
if (changed) this.lastFiredStore.save(state);
|
|
207
|
+
}
|
|
208
|
+
async fireEntry(entry, now, state, notify) {
|
|
209
|
+
const lastFired = state.get(entry.id);
|
|
210
|
+
const searchFrom = lastFired ? new Date(lastFired.getTime() + 6e4) : now;
|
|
211
|
+
if (searchFrom.getTime() > now.getTime()) return false;
|
|
212
|
+
if (entry.catchupPolicy === "skip") {
|
|
213
|
+
try {
|
|
214
|
+
if (!matchCron(entry.cron, now)) return false;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
this.logInvalidCron(entry, error);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
await this.notifyOne(entry, now, notify, false);
|
|
220
|
+
state.set(entry.id, now);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (entry.catchupPolicy === "all") {
|
|
224
|
+
const matches = this.findAllMatches(entry.cron, searchFrom, now, entry.id);
|
|
225
|
+
if (matches.length === 0) return false;
|
|
226
|
+
for (const match of matches) await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime());
|
|
227
|
+
state.set(entry.id, matches[matches.length - 1] ?? now);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
const match = this.findMostRecentMatch(entry.cron, searchFrom, now, entry.id);
|
|
231
|
+
if (!match) return false;
|
|
232
|
+
await this.notifyOne(entry, match, notify, match.getTime() !== now.getTime());
|
|
233
|
+
state.set(entry.id, match);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
async notifyOne(entry, firedAt, notify, catchup) {
|
|
237
|
+
const meta = {
|
|
238
|
+
event_type: "schedule",
|
|
239
|
+
schedule_id: entry.id,
|
|
240
|
+
cron: entry.cron,
|
|
241
|
+
fired_at: firedAt.toISOString(),
|
|
242
|
+
catchup_policy: entry.catchupPolicy
|
|
243
|
+
};
|
|
244
|
+
if (catchup) meta.catchup = "true";
|
|
245
|
+
await notify(entry.prompt, meta);
|
|
246
|
+
}
|
|
247
|
+
findMostRecentMatch(cron, from, until, entryId) {
|
|
248
|
+
const maxIterations = Math.min(MAX_CATCHUP_MINUTES, Math.floor((until.getTime() - from.getTime()) / 6e4) + 1);
|
|
249
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
250
|
+
const candidate = /* @__PURE__ */ new Date(until.getTime() - i * 6e4);
|
|
251
|
+
try {
|
|
252
|
+
if (matchCron(cron, candidate)) return candidate;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
this.logInvalidCron({
|
|
255
|
+
id: entryId,
|
|
256
|
+
cron
|
|
257
|
+
}, error);
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
findAllMatches(cron, from, until, entryId) {
|
|
264
|
+
const maxIterations = Math.min(MAX_CATCHUP_MINUTES, Math.floor((until.getTime() - from.getTime()) / 6e4) + 1);
|
|
265
|
+
const matches = [];
|
|
266
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
267
|
+
const candidate = new Date(from.getTime() + i * 6e4);
|
|
268
|
+
if (candidate.getTime() > until.getTime()) break;
|
|
269
|
+
try {
|
|
270
|
+
if (matchCron(cron, candidate)) matches.push(candidate);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
this.logInvalidCron({
|
|
273
|
+
id: entryId,
|
|
274
|
+
cron
|
|
275
|
+
}, error);
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return matches;
|
|
280
|
+
}
|
|
281
|
+
logInvalidCron(entry, error) {
|
|
282
|
+
this.logger.error("invalid cron expression in schedule", {
|
|
283
|
+
connector: this.config.name,
|
|
284
|
+
id: entry.id,
|
|
285
|
+
cron: entry.cron,
|
|
286
|
+
error: error instanceof Error ? error.message : String(error)
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
truncateToMinute(date) {
|
|
290
|
+
const copy = new Date(date.getTime());
|
|
291
|
+
copy.setSeconds(0, 0);
|
|
292
|
+
return copy;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region lib/connectors/schedule-connector-schema.ts
|
|
297
|
+
/**
|
|
298
|
+
* Catch-up behavior when the daemon was down past one or more matching minutes.
|
|
299
|
+
*
|
|
300
|
+
* - `latest`: fire once with the most recent missed match (default; preserves prior behavior).
|
|
301
|
+
* - `all`: fire once per missed minute, oldest first (capped at 24 h).
|
|
302
|
+
* - `skip`: never fire missed matches; only fire when the current minute matches.
|
|
303
|
+
*/
|
|
304
|
+
const scheduleCatchupPolicySchema = z.enum([
|
|
305
|
+
"latest",
|
|
306
|
+
"all",
|
|
307
|
+
"skip"
|
|
308
|
+
]);
|
|
309
|
+
const scheduleEntrySchema = z.object({
|
|
310
|
+
id: z.string(),
|
|
311
|
+
cron: z.string(),
|
|
312
|
+
prompt: z.string(),
|
|
313
|
+
enabled: z.boolean().default(true),
|
|
314
|
+
catchupPolicy: scheduleCatchupPolicySchema.default("latest")
|
|
315
|
+
});
|
|
316
|
+
const scheduleConnectorSchema = z.object({
|
|
317
|
+
id: z.string(),
|
|
318
|
+
name: z.string(),
|
|
319
|
+
type: z.literal("schedule"),
|
|
320
|
+
entries: z.array(scheduleEntrySchema).default([]),
|
|
321
|
+
createdAt: z.string().datetime().optional(),
|
|
322
|
+
updatedAt: z.string().datetime().optional()
|
|
323
|
+
});
|
|
324
|
+
//#endregion
|
|
325
|
+
export { ScheduleStateStore as a, matchCron as c, FunnelScheduleListener as i, scheduleConnectorSchema as n, NodeFunnelFileSystem as o, scheduleEntrySchema as r, FunnelFileSystem as s, scheduleCatchupPolicySchema as t };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { t as FunnelConnectorAdapter } from "./connector-adapter-D5Utumgz.js";
|
|
2
|
+
import { r as FunnelConnectorListener, t as NodeFunnelLogger } from "./node-logger-DQz_BGOD.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { WebClient } from "@slack/web-api";
|
|
5
|
+
import { App, LogLevel } from "@slack/bolt";
|
|
6
|
+
//#region lib/connectors/slack-adapter.ts
|
|
7
|
+
const toRecord = (value) => {
|
|
8
|
+
const result = {};
|
|
9
|
+
for (const [key, val] of Object.entries(value)) result[key] = val;
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
var FunnelSlackAdapter = class extends FunnelConnectorAdapter {
|
|
13
|
+
client;
|
|
14
|
+
constructor(deps) {
|
|
15
|
+
super();
|
|
16
|
+
this.client = deps.client ?? new WebClient(deps.config.botToken);
|
|
17
|
+
Object.freeze(this);
|
|
18
|
+
}
|
|
19
|
+
async call(input) {
|
|
20
|
+
const body = input.body !== null && typeof input.body === "object" ? toRecord(input.body) : {};
|
|
21
|
+
return await this.client.apiCall(input.path, body);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region lib/connectors/slack-event-processor.ts
|
|
26
|
+
const ALLOWED_EVENTS = new Set(["message", "app_mention"]);
|
|
27
|
+
const ALLOWED_SUBTYPES = new Set([
|
|
28
|
+
void 0,
|
|
29
|
+
"thread_broadcast",
|
|
30
|
+
"bot_message",
|
|
31
|
+
"file_share"
|
|
32
|
+
]);
|
|
33
|
+
const DEDUP_WINDOW = 1e4;
|
|
34
|
+
const getString = (event, key) => {
|
|
35
|
+
const value = event[key];
|
|
36
|
+
return typeof value === "string" ? value : void 0;
|
|
37
|
+
};
|
|
38
|
+
var FunnelSlackEventProcessor = class {
|
|
39
|
+
ownBotUserId;
|
|
40
|
+
ownBotId;
|
|
41
|
+
now;
|
|
42
|
+
dedup = /* @__PURE__ */ new Map();
|
|
43
|
+
constructor(props) {
|
|
44
|
+
this.ownBotUserId = props.ownBotUserId;
|
|
45
|
+
this.ownBotId = props.ownBotId;
|
|
46
|
+
this.now = props.now ?? (() => Date.now());
|
|
47
|
+
}
|
|
48
|
+
process(event) {
|
|
49
|
+
const eventType = getString(event, "type");
|
|
50
|
+
if (!eventType || !ALLOWED_EVENTS.has(eventType)) return { skip: true };
|
|
51
|
+
const subtype = getString(event, "subtype");
|
|
52
|
+
if (!ALLOWED_SUBTYPES.has(subtype)) return { skip: true };
|
|
53
|
+
const channelId = getString(event, "channel") ?? "";
|
|
54
|
+
const dedupKey = `${channelId}:${getString(event, "event_ts") ?? getString(event, "ts") ?? ""}`;
|
|
55
|
+
const now = this.now();
|
|
56
|
+
if (this.dedup.has(dedupKey)) return { skip: true };
|
|
57
|
+
this.dedup.set(dedupKey, now);
|
|
58
|
+
for (const key of this.dedup.keys()) if ((this.dedup.get(key) ?? 0) < now - DEDUP_WINDOW) this.dedup.delete(key);
|
|
59
|
+
const userId = getString(event, "user");
|
|
60
|
+
const botId = getString(event, "bot_id");
|
|
61
|
+
if (userId === this.ownBotUserId) return { skip: true };
|
|
62
|
+
if (botId === this.ownBotId) return { skip: true };
|
|
63
|
+
const mentioned = (getString(event, "text") ?? "").includes(`<@${this.ownBotUserId}>`);
|
|
64
|
+
const threadTs = getString(event, "thread_ts") ?? getString(event, "ts") ?? "";
|
|
65
|
+
return {
|
|
66
|
+
skip: false,
|
|
67
|
+
content: JSON.stringify(event),
|
|
68
|
+
meta: {
|
|
69
|
+
event_type: "slack",
|
|
70
|
+
channel_id: channelId,
|
|
71
|
+
user_id: userId ?? "",
|
|
72
|
+
mentioned: String(mentioned),
|
|
73
|
+
thread_ts: threadTs
|
|
74
|
+
},
|
|
75
|
+
shouldReact: mentioned,
|
|
76
|
+
channel: channelId,
|
|
77
|
+
timestamp: getString(event, "ts") ?? ""
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region lib/connectors/slack-listener.ts
|
|
83
|
+
const middlewareArgsSchema = z.object({ event: z.record(z.string(), z.unknown()).optional() });
|
|
84
|
+
const defaultLogger = new NodeFunnelLogger();
|
|
85
|
+
var FunnelSlackListener = class extends FunnelConnectorListener {
|
|
86
|
+
config;
|
|
87
|
+
logger;
|
|
88
|
+
app = null;
|
|
89
|
+
constructor(deps) {
|
|
90
|
+
super();
|
|
91
|
+
this.config = deps.config;
|
|
92
|
+
this.logger = deps.logger ?? defaultLogger;
|
|
93
|
+
}
|
|
94
|
+
async start(notify) {
|
|
95
|
+
const app = new App({
|
|
96
|
+
token: this.config.botToken,
|
|
97
|
+
appToken: this.config.appToken,
|
|
98
|
+
socketMode: true,
|
|
99
|
+
logLevel: LogLevel.ERROR
|
|
100
|
+
});
|
|
101
|
+
const authResult = await app.client.auth.test({ token: this.config.botToken });
|
|
102
|
+
const processor = new FunnelSlackEventProcessor({
|
|
103
|
+
ownBotUserId: authResult.user_id ?? "",
|
|
104
|
+
ownBotId: authResult.bot_id ?? ""
|
|
105
|
+
});
|
|
106
|
+
app.use(async (args) => {
|
|
107
|
+
const parsed = middlewareArgsSchema.safeParse(args);
|
|
108
|
+
if (!parsed.success || !parsed.data.event) return;
|
|
109
|
+
const result = processor.process(parsed.data.event);
|
|
110
|
+
if (result.skip) return;
|
|
111
|
+
if (result.shouldReact) try {
|
|
112
|
+
await app.client.reactions.add({
|
|
113
|
+
token: this.config.botToken,
|
|
114
|
+
channel: result.channel,
|
|
115
|
+
timestamp: result.timestamp,
|
|
116
|
+
name: "eyes"
|
|
117
|
+
});
|
|
118
|
+
} catch {}
|
|
119
|
+
await notify(result.content, result.meta);
|
|
120
|
+
});
|
|
121
|
+
app.error(async (error) => {
|
|
122
|
+
this.logger.error("Slack error", { error: error instanceof Error ? error.message : String(error) });
|
|
123
|
+
});
|
|
124
|
+
await app.start();
|
|
125
|
+
this.app = app;
|
|
126
|
+
}
|
|
127
|
+
async stop() {
|
|
128
|
+
if (!this.app) return;
|
|
129
|
+
try {
|
|
130
|
+
await this.app.stop();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.error("Slack stop error", { error: error instanceof Error ? error.message : String(error) });
|
|
133
|
+
} finally {
|
|
134
|
+
this.app = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
isAlive() {
|
|
138
|
+
return this.app !== null;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region lib/connectors/slack-connector-schema.ts
|
|
143
|
+
const slackConnectorSchema = z.object({
|
|
144
|
+
id: z.string(),
|
|
145
|
+
name: z.string(),
|
|
146
|
+
type: z.literal("slack"),
|
|
147
|
+
botToken: z.string().startsWith("xoxb-"),
|
|
148
|
+
appToken: z.string().startsWith("xapp-"),
|
|
149
|
+
createdAt: z.string().datetime().optional(),
|
|
150
|
+
updatedAt: z.string().datetime().optional()
|
|
151
|
+
});
|
|
152
|
+
//#endregion
|
|
153
|
+
export { FunnelSlackAdapter as i, FunnelSlackListener as n, FunnelSlackEventProcessor as r, slackConnectorSchema as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region lib/connectors/slack-connector-schema.d.ts
|
|
4
|
+
declare const slackConnectorSchema: z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
type: z.ZodLiteral<"slack">;
|
|
8
|
+
botToken: z.ZodString;
|
|
9
|
+
appToken: z.ZodString;
|
|
10
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
11
|
+
updatedAt: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
type SlackConnectorConfig = z.infer<typeof slackConnectorSchema>;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region lib/connectors/slack-event-processor.d.ts
|
|
16
|
+
type SlackRawEvent = Record<string, unknown>;
|
|
17
|
+
type SlackProcessedSkip = {
|
|
18
|
+
skip: true;
|
|
19
|
+
};
|
|
20
|
+
type SlackProcessedEmit = {
|
|
21
|
+
skip: false;
|
|
22
|
+
content: string;
|
|
23
|
+
meta: Record<string, string>;
|
|
24
|
+
shouldReact: boolean;
|
|
25
|
+
channel: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
};
|
|
28
|
+
type SlackProcessed = SlackProcessedSkip | SlackProcessedEmit;
|
|
29
|
+
type Props = {
|
|
30
|
+
ownBotUserId: string;
|
|
31
|
+
ownBotId: string;
|
|
32
|
+
now?: () => number;
|
|
33
|
+
};
|
|
34
|
+
declare class FunnelSlackEventProcessor {
|
|
35
|
+
private readonly ownBotUserId;
|
|
36
|
+
private readonly ownBotId;
|
|
37
|
+
private readonly now;
|
|
38
|
+
private readonly dedup;
|
|
39
|
+
constructor(props: Props);
|
|
40
|
+
process(event: SlackRawEvent): SlackProcessed;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { SlackRawEvent as a, SlackProcessedSkip as i, SlackProcessed as n, SlackConnectorConfig as o, SlackProcessedEmit as r, slackConnectorSchema as s, FunnelSlackEventProcessor as t };
|