@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.
- package/dist/bin.js +448 -448
- package/dist/connectors/slack.d.ts +1 -29
- package/dist/gateway/daemon.js +166 -166
- package/dist/index.d.ts +4 -11
- package/dist/index.js +133 -120
- package/dist/slack-event-processor-CS-bAit9.d.ts +43 -0
- package/package.json +1 -6
- package/dist/slack-connector-schema-D7zAHN8k.d.ts +0 -15
- package/lib/bin.ts +0 -3
- package/lib/cli/factory.ts +0 -10
- package/lib/cli/index.ts +0 -85
- package/lib/cli/router/query-to-cli-args.ts +0 -20
- package/lib/cli/router/to-request.ts +0 -113
- 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.publish.ts +0 -52
- 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 -70
- 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 -219
- 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 -49
- package/lib/cli/routes/profiles.remove.$profile.ts +0 -20
- package/lib/cli/routes/profiles.set.$profile.ts +0 -45
- 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/discord.ts +0 -4
- 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/gh.ts +0 -3
- 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/schedule.ts +0 -4
- 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/connectors/slack.ts +0 -4
- package/lib/engine/channels/channels.ts +0 -520
- package/lib/engine/claude/claude.ts +0 -205
- 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 -123
- package/lib/engine/mcp/channel-subscriber.ts +0 -82
- package/lib/engine/mcp/mcp.ts +0 -126
- package/lib/engine/mcp/read-channel-connectors.ts +0 -34
- package/lib/engine/mcp/read-gateway-token.ts +0 -16
- package/lib/engine/mcp/usage-hint-for-type.ts +0 -15
- 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 -48
- 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 -294
- package/lib/gateway/auth-middleware.ts +0 -44
- package/lib/gateway/broadcaster.ts +0 -319
- package/lib/gateway/channel-publisher.ts +0 -67
- 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 -426
- 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/publish-schema.ts +0 -27
- package/lib/gateway/resolve-daemon-script.ts +0 -26
- package/lib/gateway/routes/channels.connectors.call.ts +0 -39
- package/lib/gateway/routes/channels.publish.ts +0 -44
- package/lib/gateway/routes/health.ts +0 -13
- package/lib/gateway/routes/index.ts +0 -26
- 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 -19
- package/lib/gateway/routes/status.ts +0 -15
- package/lib/gateway/routes/validator.ts +0 -17
- package/lib/index.ts +0 -67
- 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
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { FunnelConnectorAdapter } from "@/connectors/connector-adapter"
|
|
2
|
-
import type { ConnectorConfig } from "@/connectors/connector-config-schema"
|
|
3
|
-
import type { FunnelConnectorListener } from "@/connectors/connector-listener"
|
|
4
|
-
import { FunnelDiscordAdapter } from "@/connectors/discord-adapter"
|
|
5
|
-
import { FunnelDiscordListener } from "@/connectors/discord-listener"
|
|
6
|
-
import { FunnelGhAdapter } from "@/connectors/gh-adapter"
|
|
7
|
-
import { FunnelGhListener } from "@/connectors/gh-listener"
|
|
8
|
-
import { FunnelScheduleListener } from "@/connectors/schedule-listener"
|
|
9
|
-
import { ScheduleStateStore } from "@/connectors/schedule-state-store"
|
|
10
|
-
import { FunnelSlackAdapter } from "@/connectors/slack-adapter"
|
|
11
|
-
import { FunnelSlackListener } from "@/connectors/slack-listener"
|
|
12
|
-
import { FunnelFileSystem } from "@/engine/fs/file-system"
|
|
13
|
-
import { NodeFunnelFileSystem } from "@/engine/fs/node-file-system"
|
|
14
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
15
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
16
|
-
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
17
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
18
|
-
import { FUNNEL_DIR } from "@/engine/settings/settings-store"
|
|
19
|
-
import { join } from "node:path"
|
|
20
|
-
|
|
21
|
-
type Deps = {
|
|
22
|
-
fs?: FunnelFileSystem
|
|
23
|
-
process?: FunnelProcessRunner
|
|
24
|
-
logger?: FunnelLogger
|
|
25
|
-
dir?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const defaultFs = new NodeFunnelFileSystem()
|
|
29
|
-
const defaultProcess = new NodeFunnelProcessRunner()
|
|
30
|
-
const defaultLogger = new NodeFunnelLogger()
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Pure factory for per-type listeners and adapters. The factory has no CRUD
|
|
34
|
-
* responsibility — connector configs live inside settings.json under their
|
|
35
|
-
* channel, and FunnelChannels passes them in by value.
|
|
36
|
-
*
|
|
37
|
-
* `dir` is the funnel home (defaults to ~/.funnel); per-connector state files
|
|
38
|
-
* land at `<dir>/channels/<channel-id>/connectors/<connector-id>/state.json`.
|
|
39
|
-
*/
|
|
40
|
-
export class FunnelConnectorFactory {
|
|
41
|
-
private readonly fs: FunnelFileSystem
|
|
42
|
-
private readonly process: FunnelProcessRunner
|
|
43
|
-
private readonly logger: FunnelLogger
|
|
44
|
-
private readonly dir: string
|
|
45
|
-
|
|
46
|
-
constructor(deps: Deps = {}) {
|
|
47
|
-
this.fs = deps.fs ?? defaultFs
|
|
48
|
-
this.process = deps.process ?? defaultProcess
|
|
49
|
-
this.logger = deps.logger ?? defaultLogger
|
|
50
|
-
this.dir = deps.dir ?? FUNNEL_DIR
|
|
51
|
-
Object.freeze(this)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
createListener(channelId: string, config: ConnectorConfig): FunnelConnectorListener {
|
|
55
|
-
if (config.type === "slack") {
|
|
56
|
-
return new FunnelSlackListener({ config, logger: this.logger })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (config.type === "gh") {
|
|
60
|
-
return new FunnelGhListener({ config, process: this.process, logger: this.logger })
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (config.type === "discord") {
|
|
64
|
-
return new FunnelDiscordListener({ config, logger: this.logger })
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const lastFiredStore = new ScheduleStateStore({
|
|
68
|
-
path: join(this.connectorDir(channelId, config.id), "state.json"),
|
|
69
|
-
fs: this.fs,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return new FunnelScheduleListener({
|
|
73
|
-
config,
|
|
74
|
-
lastFiredStore,
|
|
75
|
-
logger: this.logger,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
createAdapter(config: ConnectorConfig): FunnelConnectorAdapter | null {
|
|
80
|
-
if (config.type === "slack") return new FunnelSlackAdapter({ config })
|
|
81
|
-
if (config.type === "gh") return new FunnelGhAdapter({ process: this.process })
|
|
82
|
-
if (config.type === "discord") return new FunnelDiscordAdapter({ config })
|
|
83
|
-
|
|
84
|
-
return null
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
connectorDir(channelId: string, connectorId: string): string {
|
|
88
|
-
return join(this.dir, "channels", channelId, "connectors", connectorId)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
channelDir(channelId: string): string {
|
|
92
|
-
return join(this.dir, "channels", channelId)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type NotifyFn = (content: string, meta?: Record<string, string>) => Promise<void>
|
|
2
|
-
|
|
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
|
-
export abstract class FunnelConnectorListener {
|
|
15
|
-
abstract start(notify: NotifyFn): Promise<void>
|
|
16
|
-
abstract stop(): Promise<void>
|
|
17
|
-
isAlive(): boolean {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
|
|
2
|
-
import { FunnelHttpClient } from "@/engine/http/http-client"
|
|
3
|
-
import { NodeFunnelHttpClient } from "@/engine/http/node-http-client"
|
|
4
|
-
import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
|
|
5
|
-
|
|
6
|
-
const DISCORD_API_BASE = "https://discord.com/api/v10"
|
|
7
|
-
|
|
8
|
-
type Deps = {
|
|
9
|
-
config: DiscordConnectorConfig
|
|
10
|
-
http?: FunnelHttpClient
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const defaultHttp = new NodeFunnelHttpClient()
|
|
14
|
-
|
|
15
|
-
export class FunnelDiscordAdapter extends FunnelConnectorAdapter {
|
|
16
|
-
private readonly token: string
|
|
17
|
-
private readonly http: FunnelHttpClient
|
|
18
|
-
|
|
19
|
-
constructor(deps: Deps) {
|
|
20
|
-
super()
|
|
21
|
-
this.token = deps.config.botToken
|
|
22
|
-
this.http = deps.http ?? defaultHttp
|
|
23
|
-
Object.freeze(this)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async call(input: CallInput): Promise<unknown> {
|
|
27
|
-
const method = (input.method || "GET").toUpperCase()
|
|
28
|
-
const path = input.path.startsWith("/") ? input.path : `/${input.path}`
|
|
29
|
-
const body = input.body
|
|
30
|
-
const hasBody =
|
|
31
|
-
body !== null && typeof body === "object" && method !== "GET" && Object.keys(body).length > 0
|
|
32
|
-
|
|
33
|
-
const res = await this.http.fetch({
|
|
34
|
-
method,
|
|
35
|
-
url: `${DISCORD_API_BASE}${path}`,
|
|
36
|
-
headers: {
|
|
37
|
-
Authorization: `Bot ${this.token}`,
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
},
|
|
40
|
-
body: hasBody ? JSON.stringify(input.body) : undefined,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
throw new Error(`Discord API failed (${res.status}): ${await res.text()}`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (res.status === 204) return null
|
|
48
|
-
|
|
49
|
-
return await res.json()
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const discordConnectorSchema = z.object({
|
|
4
|
-
id: z.string(),
|
|
5
|
-
name: z.string(),
|
|
6
|
-
type: z.literal("discord"),
|
|
7
|
-
botToken: z.string().min(10),
|
|
8
|
-
createdAt: z.string().datetime().optional(),
|
|
9
|
-
updatedAt: z.string().datetime().optional(),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
export type DiscordConnectorConfig = z.infer<typeof discordConnectorSchema>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export type DiscordInboundMessage = {
|
|
2
|
-
authorId: string
|
|
3
|
-
authorIsBot: boolean
|
|
4
|
-
channelId: string
|
|
5
|
-
guildId: string | null
|
|
6
|
-
mentionedUserIds: string[]
|
|
7
|
-
raw: unknown
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type DiscordProcessedSkip = { skip: true }
|
|
11
|
-
|
|
12
|
-
export type DiscordProcessedEmit = {
|
|
13
|
-
skip: false
|
|
14
|
-
content: string
|
|
15
|
-
meta: Record<string, string>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type DiscordProcessed = DiscordProcessedSkip | DiscordProcessedEmit
|
|
19
|
-
|
|
20
|
-
type Props = {
|
|
21
|
-
ownUserId: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class FunnelDiscordEventProcessor {
|
|
25
|
-
private readonly ownUserId: string
|
|
26
|
-
|
|
27
|
-
constructor(props: Props) {
|
|
28
|
-
this.ownUserId = props.ownUserId
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
process(message: DiscordInboundMessage): DiscordProcessed {
|
|
32
|
-
if (message.authorIsBot) return { skip: true }
|
|
33
|
-
|
|
34
|
-
const mentioned = this.ownUserId ? message.mentionedUserIds.includes(this.ownUserId) : false
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
skip: false,
|
|
38
|
-
content: JSON.stringify(message.raw),
|
|
39
|
-
meta: {
|
|
40
|
-
event_type: "discord",
|
|
41
|
-
channel_id: message.channelId,
|
|
42
|
-
user_id: message.authorId,
|
|
43
|
-
mentioned: String(mentioned),
|
|
44
|
-
guild_id: message.guildId ?? "",
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Client, GatewayIntentBits, Partials } from "discord.js"
|
|
2
|
-
import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
|
|
3
|
-
import { FunnelDiscordEventProcessor } from "@/connectors/discord-event-processor"
|
|
4
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
5
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
6
|
-
import type { DiscordConnectorConfig } from "@/connectors/discord-connector-schema"
|
|
7
|
-
|
|
8
|
-
type Deps = {
|
|
9
|
-
config: DiscordConnectorConfig
|
|
10
|
-
logger?: FunnelLogger
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const defaultLogger = new NodeFunnelLogger()
|
|
14
|
-
|
|
15
|
-
export class FunnelDiscordListener extends FunnelConnectorListener {
|
|
16
|
-
private readonly config: DiscordConnectorConfig
|
|
17
|
-
private readonly logger: FunnelLogger
|
|
18
|
-
private client: Client | null = null
|
|
19
|
-
|
|
20
|
-
constructor(deps: Deps) {
|
|
21
|
-
super()
|
|
22
|
-
this.config = deps.config
|
|
23
|
-
this.logger = deps.logger ?? defaultLogger
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async start(notify: NotifyFn): Promise<void> {
|
|
27
|
-
const client = new Client({
|
|
28
|
-
intents: [
|
|
29
|
-
GatewayIntentBits.Guilds,
|
|
30
|
-
GatewayIntentBits.GuildMessages,
|
|
31
|
-
GatewayIntentBits.MessageContent,
|
|
32
|
-
GatewayIntentBits.DirectMessages,
|
|
33
|
-
],
|
|
34
|
-
partials: [Partials.Channel],
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
client.on("messageCreate", async (message) => {
|
|
38
|
-
const ownUserId = client.user?.id ?? ""
|
|
39
|
-
const mentionedUserIds = [...message.mentions.users.keys()]
|
|
40
|
-
|
|
41
|
-
this.logger.info("discord messageCreate", {
|
|
42
|
-
author: message.author.id,
|
|
43
|
-
authorIsBot: String(message.author.bot),
|
|
44
|
-
channelId: message.channelId,
|
|
45
|
-
guildId: message.guildId ?? "",
|
|
46
|
-
mentions: mentionedUserIds.join(","),
|
|
47
|
-
ownUserId,
|
|
48
|
-
mentioned: String(mentionedUserIds.includes(ownUserId)),
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const processor = new FunnelDiscordEventProcessor({ ownUserId })
|
|
52
|
-
|
|
53
|
-
const result = processor.process({
|
|
54
|
-
authorId: message.author.id,
|
|
55
|
-
authorIsBot: message.author.bot,
|
|
56
|
-
channelId: message.channelId,
|
|
57
|
-
guildId: message.guildId,
|
|
58
|
-
mentionedUserIds,
|
|
59
|
-
raw: message.toJSON(),
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (result.skip) {
|
|
63
|
-
this.logger.info("discord skip", { reason: "bot author" })
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
await notify(result.content, result.meta)
|
|
69
|
-
} catch (error) {
|
|
70
|
-
this.logger.error("discord notify error", {
|
|
71
|
-
error: error instanceof Error ? error.message : String(error),
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
client.on("ready", (readyClient) => {
|
|
77
|
-
this.logger.info("discord ready", {
|
|
78
|
-
userId: readyClient.user.id,
|
|
79
|
-
tag: readyClient.user.tag,
|
|
80
|
-
guilds: String(readyClient.guilds.cache.size),
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
client.on("error", (error) => {
|
|
85
|
-
this.logger.error("discord client error", {
|
|
86
|
-
error: error instanceof Error ? error.message : String(error),
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
await client.login(this.config.botToken)
|
|
91
|
-
this.client = client
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async stop(): Promise<void> {
|
|
95
|
-
if (!this.client) return
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
await this.client.destroy()
|
|
99
|
-
} catch (error) {
|
|
100
|
-
this.logger.error("discord stop error", {
|
|
101
|
-
error: error instanceof Error ? error.message : String(error),
|
|
102
|
-
})
|
|
103
|
-
} finally {
|
|
104
|
-
this.client = null
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
override isAlive(): boolean {
|
|
109
|
-
return this.client !== null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { FunnelConnectorAdapter, type CallInput } from "@/connectors/connector-adapter"
|
|
2
|
-
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
3
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
4
|
-
|
|
5
|
-
type Deps = {
|
|
6
|
-
process?: FunnelProcessRunner
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const defaultProcess = new NodeFunnelProcessRunner()
|
|
10
|
-
|
|
11
|
-
export class FunnelGhAdapter extends FunnelConnectorAdapter {
|
|
12
|
-
private readonly process: FunnelProcessRunner
|
|
13
|
-
|
|
14
|
-
constructor(deps: Deps = {}) {
|
|
15
|
-
super()
|
|
16
|
-
this.process = deps.process ?? defaultProcess
|
|
17
|
-
Object.freeze(this)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async call(input: CallInput): Promise<unknown> {
|
|
21
|
-
const args = ["api", input.path]
|
|
22
|
-
|
|
23
|
-
if (input.method && input.method.toLowerCase() !== "get") {
|
|
24
|
-
args.push("-X", input.method.toUpperCase())
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const hasBody =
|
|
28
|
-
input.body && typeof input.body === "object" && Object.keys(input.body).length > 0
|
|
29
|
-
|
|
30
|
-
if (hasBody) {
|
|
31
|
-
args.push("--input", "-")
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const result = await this.process.run(["gh", ...args], {
|
|
35
|
-
input: hasBody ? JSON.stringify(input.body) : undefined,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
if (result.exitCode !== 0) {
|
|
39
|
-
throw new Error(`gh api failed: ${result.stderr.trim() || result.stdout.trim()}`)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
return JSON.parse(result.stdout)
|
|
44
|
-
} catch {
|
|
45
|
-
return result.stdout
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
3
|
-
export const ghConnectorSchema = z.object({
|
|
4
|
-
id: z.string(),
|
|
5
|
-
name: z.string(),
|
|
6
|
-
type: z.literal("gh"),
|
|
7
|
-
pollInterval: z.number().int().positive().optional(),
|
|
8
|
-
createdAt: z.string().datetime().optional(),
|
|
9
|
-
updatedAt: z.string().datetime().optional(),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
export type GhConnectorConfig = z.infer<typeof ghConnectorSchema>
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
import { FunnelConnectorListener, type NotifyFn } from "@/connectors/connector-listener"
|
|
3
|
-
import { FunnelLogger } from "@/engine/logger/logger"
|
|
4
|
-
import { NodeFunnelLogger } from "@/engine/logger/node-logger"
|
|
5
|
-
import { FunnelProcessRunner } from "@/engine/process/process-runner"
|
|
6
|
-
import { NodeFunnelProcessRunner } from "@/engine/process/node-process-runner"
|
|
7
|
-
import type { GhConnectorConfig } from "@/connectors/gh-connector-schema"
|
|
8
|
-
|
|
9
|
-
const ghNotificationSchema = z.object({
|
|
10
|
-
id: z.string(),
|
|
11
|
-
reason: z.string(),
|
|
12
|
-
subject: z.object({
|
|
13
|
-
type: z.string(),
|
|
14
|
-
url: z.string(),
|
|
15
|
-
title: z.string(),
|
|
16
|
-
}),
|
|
17
|
-
repository: z.object({ full_name: z.string() }),
|
|
18
|
-
updated_at: z.string(),
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const ghNotificationsSchema = z.array(ghNotificationSchema)
|
|
22
|
-
|
|
23
|
-
type GhNotification = z.infer<typeof ghNotificationSchema>
|
|
24
|
-
|
|
25
|
-
type Deps = {
|
|
26
|
-
config: GhConnectorConfig
|
|
27
|
-
process?: FunnelProcessRunner
|
|
28
|
-
logger?: FunnelLogger
|
|
29
|
-
now?: () => Date
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const defaultProcess = new NodeFunnelProcessRunner()
|
|
33
|
-
const defaultLogger = new NodeFunnelLogger()
|
|
34
|
-
|
|
35
|
-
const MAX_SEEN = 10000
|
|
36
|
-
const KEEP_SEEN = 5000
|
|
37
|
-
|
|
38
|
-
export class FunnelGhListener extends FunnelConnectorListener {
|
|
39
|
-
private readonly config: GhConnectorConfig
|
|
40
|
-
private readonly process: FunnelProcessRunner
|
|
41
|
-
private readonly logger: FunnelLogger
|
|
42
|
-
private readonly now: () => Date
|
|
43
|
-
private readonly seen = new Map<string, string>()
|
|
44
|
-
private bootstrapped = false
|
|
45
|
-
private since: string
|
|
46
|
-
private timer: ReturnType<typeof setInterval> | null = null
|
|
47
|
-
|
|
48
|
-
constructor(deps: Deps) {
|
|
49
|
-
super()
|
|
50
|
-
this.config = deps.config
|
|
51
|
-
this.process = deps.process ?? defaultProcess
|
|
52
|
-
this.logger = deps.logger ?? defaultLogger
|
|
53
|
-
this.now = deps.now ?? (() => new Date())
|
|
54
|
-
this.since = this.now().toISOString()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async start(notify: NotifyFn): Promise<void> {
|
|
58
|
-
await this.pollOnce(notify)
|
|
59
|
-
|
|
60
|
-
const interval = this.config.pollInterval ?? 60
|
|
61
|
-
|
|
62
|
-
this.timer = setInterval(() => void this.pollOnce(notify), interval * 1000)
|
|
63
|
-
this.timer.unref()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async stop(): Promise<void> {
|
|
67
|
-
if (!this.timer) return
|
|
68
|
-
|
|
69
|
-
clearInterval(this.timer)
|
|
70
|
-
this.timer = null
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
override isAlive(): boolean {
|
|
74
|
-
return this.timer !== null
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async pollOnce(notify: NotifyFn): Promise<void> {
|
|
78
|
-
const nextSince = this.now().toISOString()
|
|
79
|
-
const params = new URLSearchParams({ since: this.since, all: "false" })
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const result = await this.process.run(["gh", "api", `/notifications?${params}`])
|
|
83
|
-
|
|
84
|
-
if (result.exitCode !== 0) {
|
|
85
|
-
this.logger.error("gh poll failed", { stderr: result.stderr })
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const parsed = ghNotificationsSchema.safeParse(JSON.parse(result.stdout))
|
|
90
|
-
|
|
91
|
-
if (!parsed.success) {
|
|
92
|
-
this.logger.warn("gh response did not match schema", { error: parsed.error.message })
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const items: GhNotification[] = parsed.data
|
|
97
|
-
|
|
98
|
-
for (const item of items) {
|
|
99
|
-
if (this.seen.get(item.id) === item.updated_at) continue
|
|
100
|
-
|
|
101
|
-
this.seen.set(item.id, item.updated_at)
|
|
102
|
-
|
|
103
|
-
if (!this.bootstrapped) continue
|
|
104
|
-
|
|
105
|
-
const meta: Record<string, string> = {
|
|
106
|
-
event_type: "gh",
|
|
107
|
-
reason: item.reason,
|
|
108
|
-
subject_type: item.subject.type,
|
|
109
|
-
subject_url: item.subject.url,
|
|
110
|
-
repository: item.repository.full_name,
|
|
111
|
-
thread_id: item.id,
|
|
112
|
-
updated_at: item.updated_at,
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await notify(JSON.stringify(item), meta)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (this.seen.size > MAX_SEEN) {
|
|
119
|
-
const toDrop = this.seen.size - KEEP_SEEN
|
|
120
|
-
let dropped = 0
|
|
121
|
-
|
|
122
|
-
for (const key of this.seen.keys()) {
|
|
123
|
-
if (dropped >= toDrop) break
|
|
124
|
-
this.seen.delete(key)
|
|
125
|
-
dropped++
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
this.since = nextSince
|
|
130
|
-
this.bootstrapped = true
|
|
131
|
-
} catch (error) {
|
|
132
|
-
this.logger.error("gh poll error", {
|
|
133
|
-
error: error instanceof Error ? error.message : String(error),
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
package/lib/connectors/gh.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
type Field = { min: number; max: number; values: Set<number> }
|
|
2
|
-
|
|
3
|
-
const parseField = (expr: string, min: number, max: number): Field => {
|
|
4
|
-
const values = new Set<number>()
|
|
5
|
-
|
|
6
|
-
for (const part of expr.split(",")) {
|
|
7
|
-
const [rangePart, stepPart] = part.split("/")
|
|
8
|
-
const step = stepPart ? Number(stepPart) : 1
|
|
9
|
-
|
|
10
|
-
if (!Number.isFinite(step) || step <= 0) {
|
|
11
|
-
throw new Error(`invalid cron step: "${stepPart}"`)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let lo = min
|
|
15
|
-
let hi = max
|
|
16
|
-
|
|
17
|
-
if (rangePart === "*" || rangePart === undefined || 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
|
-
|
|
25
|
-
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
26
|
-
throw new Error(`invalid cron range: "${rangePart}"`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
lo = a
|
|
30
|
-
hi = b
|
|
31
|
-
} else {
|
|
32
|
-
const n = Number(rangePart)
|
|
33
|
-
|
|
34
|
-
if (!Number.isFinite(n)) throw new Error(`invalid cron value: "${rangePart}"`)
|
|
35
|
-
|
|
36
|
-
lo = n
|
|
37
|
-
hi = stepPart ? max : n
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (lo < min || hi > max || lo > hi) {
|
|
41
|
-
throw new Error(`cron value out of range: ${rangePart} (must be ${min}-${max})`)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (let i = lo; i <= hi; i += step) {
|
|
45
|
-
values.add(i)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { min, max, values }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const matchCron = (expr: string, date: Date): boolean => {
|
|
53
|
-
const parts = expr.trim().split(/\s+/)
|
|
54
|
-
|
|
55
|
-
if (parts.length !== 5) {
|
|
56
|
-
throw new Error(`cron must have 5 fields (got ${parts.length}): "${expr}"`)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const [minute, hour, dom, month, dow] = parts
|
|
60
|
-
|
|
61
|
-
if (!minute || !hour || !dom || !month || !dow) {
|
|
62
|
-
throw new Error(`cron has empty fields: "${expr}"`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const fields = [
|
|
66
|
-
{ field: parseField(minute, 0, 59), value: date.getMinutes() },
|
|
67
|
-
{ field: parseField(hour, 0, 23), value: date.getHours() },
|
|
68
|
-
{ field: parseField(dom, 1, 31), value: date.getDate() },
|
|
69
|
-
{ field: parseField(month, 1, 12), value: date.getMonth() + 1 },
|
|
70
|
-
{ field: parseField(dow, 0, 6), value: date.getDay() },
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
for (const { field, value } of fields) {
|
|
74
|
-
if (!field.values.has(value)) return false
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { z } from "zod"
|
|
2
|
-
|
|
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
|
-
export const scheduleCatchupPolicySchema = z.enum(["latest", "all", "skip"])
|
|
11
|
-
|
|
12
|
-
export type ScheduleCatchupPolicy = z.infer<typeof scheduleCatchupPolicySchema>
|
|
13
|
-
|
|
14
|
-
export const scheduleEntrySchema = z.object({
|
|
15
|
-
id: z.string(),
|
|
16
|
-
cron: z.string(),
|
|
17
|
-
prompt: z.string(),
|
|
18
|
-
enabled: z.boolean().default(true),
|
|
19
|
-
catchupPolicy: scheduleCatchupPolicySchema.default("latest"),
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export type ScheduleEntry = z.infer<typeof scheduleEntrySchema>
|
|
23
|
-
|
|
24
|
-
export const scheduleConnectorSchema = z.object({
|
|
25
|
-
id: z.string(),
|
|
26
|
-
name: z.string(),
|
|
27
|
-
type: z.literal("schedule"),
|
|
28
|
-
entries: z.array(scheduleEntrySchema).default([]),
|
|
29
|
-
createdAt: z.string().datetime().optional(),
|
|
30
|
-
updatedAt: z.string().datetime().optional(),
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
export type ScheduleConnectorConfig = z.infer<typeof scheduleConnectorSchema>
|