@interactive-inc/claude-funnel 0.66.0 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/bin.js +275 -372
- package/dist/channel.d.ts +76 -52
- package/dist/channel.js +82 -47
- package/dist/{channels-DR90RmUP.d.ts → channels-BBA3GTAa.d.ts} +5 -6
- package/dist/claude.d.ts +11 -8
- package/dist/claude.js +31 -15
- package/dist/{connector-descriptor-CmyHlEiT.d.ts → connector-descriptor-F75ZI8Fh.d.ts} +26 -15
- package/dist/{connector-diagnostics-recorder-COtNEmUp.js → connector-diagnostics-recorder-BmOijU1p.js} +1 -0
- package/dist/connectors/discord.d.ts +10 -8
- package/dist/connectors/discord.js +2 -2
- package/dist/connectors/gh.d.ts +6 -6
- package/dist/connectors/gh.js +2 -2
- package/dist/connectors/schedule.d.ts +10 -8
- package/dist/connectors/schedule.js +2 -2
- package/dist/connectors/slack.d.ts +14 -10
- package/dist/connectors/slack.js +3 -3
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +1 -1
- package/dist/{discord-connector-DXARcO6w.js → discord-connector-CdjwPoty.js} +6 -6
- package/dist/{discord-connector-schema-D1dyxhXk.d.ts → discord-connector-schema-BaBZYHxK.d.ts} +0 -1
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +1 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/{file-process-guard-CiBwXjzm.d.ts → file-process-guard-DAYe9Ndt.d.ts} +22 -18
- package/dist/{file-system-DHgbuTFb.d.ts → file-system-BNtl997T.d.ts} +2 -1
- package/dist/{flume-source-listener-FzR5lGpL.d.ts → flume-source-listener-CLtsdcT2.d.ts} +8 -6
- package/dist/{funnel-diagnostics-Cvk6Sk4x.js → funnel-diagnostics-B-PCuv_E.js} +4 -3
- package/dist/{funnel-diagnostics-kbd27d7I.d.ts → funnel-diagnostics-CnKus3Jg.d.ts} +8 -5
- package/dist/{funnel-docs-C-ge0MuB.js → funnel-docs-BFcqITFA.js} +4 -0
- package/dist/{funnel-doctor-XrI2GBH8.js → funnel-doctor-Csw5qjcH.js} +1 -0
- package/dist/{funnel-doctor-CVTNUtDn.d.ts → funnel-doctor-CviUTINw.d.ts} +20 -9
- package/dist/{funnel-error-0t1MK1R6.js → funnel-error-DoSBCogD.js} +13 -0
- package/dist/{funnel-log-sqlite-sink-DLYkY0pZ.js → funnel-log-sqlite-sink-CXbs3qQp.js} +1 -1
- package/dist/{funnel-recovery-D8MkiTpk.d.ts → funnel-recovery-BwcSNNSN.d.ts} +1 -2
- package/dist/{funnel-recovery-DKnEutUS.js → funnel-recovery-DYbSYGWC.js} +1 -0
- package/dist/gateway/daemon.js +66 -125
- package/dist/gateway.d.ts +2 -3
- package/dist/gateway.js +2 -2
- package/dist/{gh-connector-BUGCOEWS.js → gh-connector-D-iyjl-d.js} +5 -5
- package/dist/{gh-connector-schema-BCHEqpUg.d.ts → gh-connector-schema-BrNsCl1T.d.ts} +0 -1
- package/dist/{index-BU1TtuFy.d.ts → index-B4RvEaW-.d.ts} +205 -40
- package/dist/index.d.ts +115 -109
- package/dist/index.js +123 -32
- package/dist/{local-config-json-schema-DexV8vX3.js → local-config-json-schema-_-YVHX4c.js} +4 -4
- package/dist/local-config.d.ts +2 -3
- package/dist/local-config.js +1 -1
- package/dist/logger.d.ts +29 -13
- package/dist/logger.js +1 -1
- package/dist/{memory-token-prompter-D0kIdhar.d.ts → memory-token-prompter-D3R_kgM_.d.ts} +2 -3
- package/dist/{node-file-system-BOXIHW_Q.js → node-file-system-xJx7dymE.js} +11 -3
- package/dist/{profiles-M24JrcOh.d.ts → profiles-CePRnv3F.d.ts} +3 -4
- package/dist/{profiles-ZHLONml4.js → profiles-KpN7_gBw.js} +19 -5
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +1 -1
- package/dist/recovery.d.ts +1 -1
- package/dist/recovery.js +1 -1
- package/dist/{schedule-connector-schema-D7ZEKvit.d.ts → schedule-connector-schema-DBko_Bf7.d.ts} +0 -1
- package/dist/{schedule-connector-9k3gOIgl.js → schedule-connector-teeDMHUe.js} +5 -5
- package/dist/{settings-reader-BnKIbmsD.d.ts → settings-reader-CyTpbAre.d.ts} +1 -2
- package/dist/{settings-schema-IShSMm5G.d.ts → settings-schema-B-CKLuST.d.ts} +0 -1
- package/dist/{settings-store-ccv291GV.js → settings-store-ZFWBhOnp.js} +12 -3
- package/dist/{slack-connector-CxpWagbT.js → slack-connector-BE0KAjCs.js} +10 -9
- package/dist/{slack-event-processor-xFDG3US0.js → slack-event-processor-Cs_YuI3w.js} +2 -2
- package/dist/{slack-event-processor-BDTr62cN.d.ts → slack-event-processor-DmAlq4cV.d.ts} +0 -1
- package/dist/{slot-fields-D-pvMgTK.js → slot-fields-5ERhytIA.js} +3 -3
- package/dist/{sqlite-diagnostic-log-CwhXE9Qh.js → sqlite-diagnostic-log-BDxfXGZy.js} +42 -19
- package/dist/{yaml-render-DVEtYuF1.js → yaml-render-DxxRQT-V.js} +3 -3
- package/package.json +10 -10
- package/dist/broadcaster-DVNcw00K.d.ts +0 -138
- package/dist/file-system-Wvzc2ePY.js +0 -9
- package/dist/logger-B6iyNbxM.js +0 -9
- package/dist/settings-reader-9FcX3qS1.js +0 -11
- /package/dist/{connector-adapter-Dvs8N7ew.js → connector-adapter-BL2ehMej.js} +0 -0
- /package/dist/{connector-listener-mPGZYa8e.js → connector-listener-BcQMzY6Q.js} +0 -0
- /package/dist/{diagnostic-sql-reader-oXZnWFf_.js → diagnostic-sql-reader-CoBk_EFF.js} +0 -0
- /package/dist/{discord-connector-schema-B4YpWpR3.js → discord-connector-schema-saAGuPmJ.js} +0 -0
- /package/dist/{error-message-of-ColuYmAk.js → error-message-of-BG4ezZ_s.js} +0 -0
- /package/dist/{funnel-docs-Cd66sKyV.d.ts → funnel-docs-BJWawfWI.d.ts} +0 -0
- /package/dist/{gh-connector-schema-CAqIhzGr.js → gh-connector-schema-DmZTrzUu.js} +0 -0
- /package/dist/{logger-Bc_Ooypb.d.ts → logger-mMZvblTx.d.ts} +0 -0
- /package/dist/{loopback-fetch-CVNuN3YZ.js → loopback-fetch-HP-bo9Kv.js} +0 -0
- /package/dist/{node-http-client-u00atiKx.js → node-http-client-SX3xFh4h.js} +0 -0
- /package/dist/{node-process-runner-DxTvycoK.js → node-process-runner-D4rzPl8Q.js} +0 -0
- /package/dist/{resolve-connector-token-DxDG9mhf.js → resolve-connector-token-BwiJCu82.js} +0 -0
- /package/dist/{schedule-connector-schema-DKEPZnVv.js → schedule-connector-schema-C2AChpwX.js} +0 -0
- /package/dist/{slack-connector-schema-Dem8to4P.js → slack-connector-schema-EGVuuqSX.js} +0 -0
package/dist/channel.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { n as FunnelClock, t as FunnelLogger } from "./logger-
|
|
2
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
3
|
-
import { r as FunnelBroadcaster } from "./broadcaster-DVNcw00K.js";
|
|
1
|
+
import { n as FunnelClock, t as FunnelLogger } from "./logger-mMZvblTx.js";
|
|
2
|
+
import { n as FunnelFileSystem } from "./file-system-BNtl997T.js";
|
|
4
3
|
import { FlumeCatchupPolicy, FlumeErrorHandler, FlumeEvent, FlumeRuntimeDeps, FlumeSource, FlumeStatePersister } from "@interactive-inc/flume";
|
|
5
|
-
|
|
6
4
|
//#region lib/engine/channel/channel.d.ts
|
|
7
5
|
/**
|
|
8
|
-
*
|
|
9
|
-
* `null`
|
|
6
|
+
* Payload shaped for `broadcaster.broadcast(content, meta)`. A transform
|
|
7
|
+
* returning `null` means "drop" (do not broadcast)
|
|
10
8
|
*/
|
|
11
9
|
type ChannelBroadcastPayload = {
|
|
12
10
|
readonly content: string;
|
|
@@ -14,21 +12,29 @@ type ChannelBroadcastPayload = {
|
|
|
14
12
|
};
|
|
15
13
|
type ChannelTransform = (event: FlumeEvent) => ChannelBroadcastPayload | null;
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Narrow broadcast seam the supervisor depends on. The gateway
|
|
16
|
+
* `FunnelBroadcaster` satisfies it structurally, so the engine layer never
|
|
17
|
+
* imports from the gateway layer
|
|
18
|
+
*/
|
|
19
|
+
type ChannelBroadcastSink = {
|
|
20
|
+
broadcast(content: string, meta?: Record<string, string>): void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* What `Channel.build(ctx)` returns: the flume sources to run plus an optional
|
|
24
|
+
* transform, handed to the supervisor as one unit
|
|
19
25
|
*/
|
|
20
26
|
type ChannelRuntime = {
|
|
21
27
|
readonly sources: ReadonlyArray<FlumeSource>;
|
|
22
28
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
29
|
+
* Converts each source's FlumeEvent into a broadcast payload. Defaults to
|
|
30
|
+
* `{ content: JSON.stringify(event.data), meta: event.meta }` when omitted
|
|
25
31
|
*/
|
|
26
32
|
readonly transform?: ChannelTransform;
|
|
27
33
|
};
|
|
28
34
|
/**
|
|
29
|
-
* Channel.build
|
|
30
|
-
*
|
|
31
|
-
* channel id
|
|
35
|
+
* Environment passed to `Channel.build`. IO boundaries stay abstract via
|
|
36
|
+
* FunnelClock / FunnelLogger / FunnelFileSystem. `statePersister` builds a
|
|
37
|
+
* funnel-fs-backed `FlumeStatePersister<S>` scoped to the channel id
|
|
32
38
|
*/
|
|
33
39
|
type ChannelBuildContext = {
|
|
34
40
|
readonly channelId: string;
|
|
@@ -38,66 +44,77 @@ type ChannelBuildContext = {
|
|
|
38
44
|
readonly clock: FunnelClock;
|
|
39
45
|
readonly fs: FunnelFileSystem;
|
|
40
46
|
/**
|
|
41
|
-
* `<funnelDir>/channels/<channelId>/<filename>.json`
|
|
42
|
-
*
|
|
47
|
+
* Reads/writes `<funnelDir>/channels/<channelId>/<filename>.json` in the
|
|
48
|
+
* `FlumeStatePersister<S>` shape flume expects
|
|
43
49
|
*/
|
|
44
50
|
readonly statePersister: <S>(filename: string) => FlumeStatePersister<S>;
|
|
45
51
|
};
|
|
46
52
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
53
|
+
* One channel = one inbound intake unit. `build` returns the sources to plug
|
|
54
|
+
* into the confluence and may be re-invoked any number of times (host abort,
|
|
55
|
+
* token rotation, ...) — treat it as a pure factory
|
|
49
56
|
*/
|
|
50
57
|
type Channel = {
|
|
51
58
|
readonly id: string;
|
|
52
59
|
readonly name?: string;
|
|
53
60
|
readonly build: (ctx: ChannelBuildContext) => ChannelRuntime | Promise<ChannelRuntime>;
|
|
54
61
|
};
|
|
55
|
-
/**
|
|
62
|
+
/** Frozen identity helper that keeps consumer type inference tidy */
|
|
56
63
|
declare function defineChannel(channel: Channel): Channel;
|
|
57
64
|
//#endregion
|
|
58
65
|
//#region lib/engine/channel/channel-supervisor.d.ts
|
|
59
|
-
type Props$
|
|
60
|
-
readonly broadcaster:
|
|
66
|
+
type Props$2 = {
|
|
67
|
+
readonly broadcaster: ChannelBroadcastSink;
|
|
61
68
|
readonly logger: FunnelLogger;
|
|
62
69
|
readonly clock: FunnelClock;
|
|
63
70
|
readonly fs: FunnelFileSystem;
|
|
64
71
|
/**
|
|
65
|
-
*
|
|
66
|
-
* `<root>/channels/<channelId>/`
|
|
72
|
+
* Root for per-channel state files. Writes go under
|
|
73
|
+
* `<root>/channels/<channelId>/`
|
|
67
74
|
*/
|
|
68
75
|
readonly dir: string;
|
|
69
76
|
readonly deps?: FlumeRuntimeDeps;
|
|
70
|
-
readonly onError?: FlumeErrorHandler;
|
|
77
|
+
readonly onError?: FlumeErrorHandler;
|
|
78
|
+
/** Host abort. Once fired the supervisor closes every channel and goes inert */
|
|
71
79
|
readonly signal?: AbortSignal;
|
|
72
80
|
};
|
|
73
81
|
/**
|
|
74
|
-
* Channel
|
|
82
|
+
* Maps the Channel manifest onto a single `FlumeConfluence`.
|
|
75
83
|
*
|
|
76
|
-
* - `register(channel)`
|
|
77
|
-
* sources
|
|
78
|
-
* - confluence
|
|
79
|
-
*
|
|
80
|
-
* - `unregister(id)`
|
|
84
|
+
* - channels accepted via `register(channel)` are `build(ctx)`-ed on `start()`
|
|
85
|
+
* and their sources inserted with `add(channelId, sources)`
|
|
86
|
+
* - the confluence onItem stream (tagged with groupId) is routed through the
|
|
87
|
+
* owning channel's `transform`, then into `broadcaster.broadcast(content, meta)`
|
|
88
|
+
* - `unregister(id)` stops one channel, `stop()` stops them all
|
|
81
89
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
90
|
+
* Independent from the ConnectorDescriptor system (`FunnelListenerRegistry`);
|
|
91
|
+
* the two run side-by-side so callers can migrate incrementally
|
|
84
92
|
*/
|
|
85
93
|
declare class FunnelChannelSupervisor {
|
|
86
94
|
private readonly props;
|
|
87
95
|
private readonly confluence;
|
|
88
96
|
private readonly registered;
|
|
89
97
|
private readonly pending;
|
|
98
|
+
private readonly opening;
|
|
90
99
|
private started;
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
private aborted;
|
|
101
|
+
constructor(props: Props$2);
|
|
102
|
+
/**
|
|
103
|
+
* Accepted before or after start (post-start registers open immediately,
|
|
104
|
+
* tracked in `opening` until settled). No-op once the host signal aborted
|
|
105
|
+
*/
|
|
93
106
|
register(channel: Channel): void;
|
|
107
|
+
/** Waits for an in-flight post-start open of the same id before removing */
|
|
94
108
|
unregister(id: string): Promise<void>;
|
|
95
109
|
start(): Promise<void>;
|
|
96
110
|
stop(): Promise<void>;
|
|
97
111
|
ids(): ReadonlyArray<string>;
|
|
98
112
|
has(id: string): boolean;
|
|
113
|
+
/** Never rejects: build/add failures are logged and the channel is skipped */
|
|
99
114
|
private openChannel;
|
|
115
|
+
private buildRuntime;
|
|
100
116
|
private handleItem;
|
|
117
|
+
/** Never throws: a throwing user transform is logged and the event dropped */
|
|
101
118
|
private transformToPayload;
|
|
102
119
|
}
|
|
103
120
|
//#endregion
|
|
@@ -105,22 +122,24 @@ declare class FunnelChannelSupervisor {
|
|
|
105
122
|
type Options = {
|
|
106
123
|
readonly id: string;
|
|
107
124
|
readonly name?: string;
|
|
108
|
-
readonly cron: string;
|
|
125
|
+
readonly cron: string;
|
|
126
|
+
/** Default: `{ mode: "lastOnly" }` (recover only the most recent missed tick on startup) */
|
|
109
127
|
readonly catchupPolicy?: FlumeCatchupPolicy;
|
|
110
128
|
/**
|
|
111
|
-
*
|
|
112
|
-
* `{ content: "tick", meta: { cron, firedAt } }`
|
|
129
|
+
* Converts each tick into a broadcast payload. Defaults to the equivalent of
|
|
130
|
+
* `{ content: "tick", meta: { cron, firedAt } }`
|
|
113
131
|
*/
|
|
114
132
|
readonly transform?: ChannelTransform;
|
|
115
133
|
/**
|
|
116
|
-
* `statePersister`
|
|
134
|
+
* Whether to enable `statePersister` (default true). `false` also disables
|
|
135
|
+
* catchup
|
|
117
136
|
*/
|
|
118
137
|
readonly persist?: boolean;
|
|
119
138
|
};
|
|
120
139
|
/**
|
|
121
|
-
*
|
|
140
|
+
* Factory for a single-cron-entry time channel.
|
|
122
141
|
*
|
|
123
|
-
*
|
|
142
|
+
* Example (inta jiho):
|
|
124
143
|
* ```ts
|
|
125
144
|
* const jiho = timeChannel({
|
|
126
145
|
* id: "inta-jiho",
|
|
@@ -132,30 +151,35 @@ type Options = {
|
|
|
132
151
|
* })
|
|
133
152
|
* ```
|
|
134
153
|
*
|
|
135
|
-
* `persist: true` (
|
|
136
|
-
*
|
|
154
|
+
* With `persist: true` (default), `lastFiredAt` is kept in `<channelDir>/time.json`
|
|
155
|
+
* and missed ticks are recovered on startup according to `catchupPolicy`
|
|
156
|
+
* (default lastOnly)
|
|
137
157
|
*/
|
|
138
158
|
declare function timeChannel(options: Options): Channel;
|
|
139
159
|
//#endregion
|
|
140
160
|
//#region lib/engine/channel/file-state-persister.d.ts
|
|
141
|
-
type Props = {
|
|
161
|
+
type Props$1 = {
|
|
142
162
|
readonly fs: FunnelFileSystem;
|
|
143
163
|
readonly path: string;
|
|
144
164
|
};
|
|
145
165
|
/**
|
|
146
|
-
* `
|
|
147
|
-
*
|
|
148
|
-
* load
|
|
166
|
+
* `FlumeStatePersister` storing one JSON document per file on top of
|
|
167
|
+
* `FunnelFileSystem`. The target directory is created recursively before save.
|
|
168
|
+
* load returns `null` when the file is absent, empty, or unparsable (flume
|
|
169
|
+
* then treats the source as first-run)
|
|
149
170
|
*/
|
|
150
|
-
declare function createFileStatePersister<S>(props: Props): FlumeStatePersister<S>;
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
declare function createFileStatePersister<S>(props: Props$1): FlumeStatePersister<S>;
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region lib/engine/channel/channel-state-persister-factory.d.ts
|
|
174
|
+
type Props = {
|
|
175
|
+
readonly fs: FunnelFileSystem;
|
|
176
|
+
/** Absolute path of `<funnelDir>/channels/<channelId>` */
|
|
153
177
|
readonly channelDir: string;
|
|
154
178
|
};
|
|
155
179
|
/**
|
|
156
|
-
* channel
|
|
157
|
-
* `<channelDir>/time.json`
|
|
180
|
+
* Per-channel state persister factory: `statePersister<S>("time")` returns a
|
|
181
|
+
* persister bound to `<channelDir>/time.json`
|
|
158
182
|
*/
|
|
159
|
-
declare function createChannelStatePersisterFactory(props:
|
|
183
|
+
declare function createChannelStatePersisterFactory(props: Props): <S>(filename: string) => FlumeStatePersister<S>;
|
|
160
184
|
//#endregion
|
|
161
|
-
export { type Channel, type ChannelBroadcastPayload, type ChannelBuildContext, type ChannelRuntime, type ChannelTransform, FunnelChannelSupervisor, createChannelStatePersisterFactory, createFileStatePersister, defineChannel, timeChannel };
|
|
185
|
+
export { type Channel, type ChannelBroadcastPayload, type ChannelBroadcastSink, type ChannelBuildContext, type ChannelRuntime, type ChannelTransform, FunnelChannelSupervisor, createChannelStatePersisterFactory, createFileStatePersister, defineChannel, timeChannel };
|
package/dist/channel.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
|
-
import { FlumeConfluence
|
|
2
|
+
import { FlumeConfluence } from "@interactive-inc/flume";
|
|
3
|
+
import { FlumeTimeSource } from "@interactive-inc/flume/time";
|
|
3
4
|
//#region lib/engine/channel/channel.ts
|
|
4
|
-
/**
|
|
5
|
+
/** Frozen identity helper that keeps consumer type inference tidy */
|
|
5
6
|
function defineChannel(channel) {
|
|
6
7
|
return Object.freeze({ ...channel });
|
|
7
8
|
}
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region lib/engine/channel/file-state-persister.ts
|
|
10
11
|
/**
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
* load
|
|
12
|
+
* `FlumeStatePersister` storing one JSON document per file on top of
|
|
13
|
+
* `FunnelFileSystem`. The target directory is created recursively before save.
|
|
14
|
+
* load returns `null` when the file is absent, empty, or unparsable (flume
|
|
15
|
+
* then treats the source as first-run)
|
|
14
16
|
*/
|
|
15
17
|
function createFileStatePersister(props) {
|
|
16
18
|
return {
|
|
@@ -31,9 +33,11 @@ function createFileStatePersister(props) {
|
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
35
|
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region lib/engine/channel/channel-state-persister-factory.ts
|
|
34
38
|
/**
|
|
35
|
-
* channel
|
|
36
|
-
* `<channelDir>/time.json`
|
|
39
|
+
* Per-channel state persister factory: `statePersister<S>("time")` returns a
|
|
40
|
+
* persister bound to `<channelDir>/time.json`
|
|
37
41
|
*/
|
|
38
42
|
function createChannelStatePersisterFactory(props) {
|
|
39
43
|
return (filename) => createFileStatePersister({
|
|
@@ -44,22 +48,25 @@ function createChannelStatePersisterFactory(props) {
|
|
|
44
48
|
//#endregion
|
|
45
49
|
//#region lib/engine/channel/channel-supervisor.ts
|
|
46
50
|
/**
|
|
47
|
-
* Channel
|
|
51
|
+
* Maps the Channel manifest onto a single `FlumeConfluence`.
|
|
48
52
|
*
|
|
49
|
-
* - `register(channel)`
|
|
50
|
-
* sources
|
|
51
|
-
* - confluence
|
|
52
|
-
*
|
|
53
|
-
* - `unregister(id)`
|
|
53
|
+
* - channels accepted via `register(channel)` are `build(ctx)`-ed on `start()`
|
|
54
|
+
* and their sources inserted with `add(channelId, sources)`
|
|
55
|
+
* - the confluence onItem stream (tagged with groupId) is routed through the
|
|
56
|
+
* owning channel's `transform`, then into `broadcaster.broadcast(content, meta)`
|
|
57
|
+
* - `unregister(id)` stops one channel, `stop()` stops them all
|
|
54
58
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
59
|
+
* Independent from the ConnectorDescriptor system (`FunnelListenerRegistry`);
|
|
60
|
+
* the two run side-by-side so callers can migrate incrementally
|
|
57
61
|
*/
|
|
58
62
|
var FunnelChannelSupervisor = class {
|
|
63
|
+
props;
|
|
59
64
|
confluence;
|
|
60
65
|
registered = /* @__PURE__ */ new Map();
|
|
61
66
|
pending = /* @__PURE__ */ new Map();
|
|
67
|
+
opening = /* @__PURE__ */ new Map();
|
|
62
68
|
started = false;
|
|
69
|
+
aborted = false;
|
|
63
70
|
constructor(props) {
|
|
64
71
|
this.props = props;
|
|
65
72
|
this.confluence = new FlumeConfluence({
|
|
@@ -67,25 +74,36 @@ var FunnelChannelSupervisor = class {
|
|
|
67
74
|
onError: props.onError,
|
|
68
75
|
deps: props.deps
|
|
69
76
|
});
|
|
70
|
-
if (props.signal)
|
|
77
|
+
if (props.signal) if (props.signal.aborted) this.aborted = true;
|
|
78
|
+
else {
|
|
71
79
|
const onAbort = () => {
|
|
80
|
+
this.aborted = true;
|
|
72
81
|
this.stop().catch(() => {});
|
|
73
82
|
};
|
|
74
|
-
|
|
75
|
-
else props.signal.addEventListener("abort", onAbort, { once: true });
|
|
83
|
+
props.signal.addEventListener("abort", onAbort, { once: true });
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Accepted before or after start (post-start registers open immediately,
|
|
88
|
+
* tracked in `opening` until settled). No-op once the host signal aborted
|
|
89
|
+
*/
|
|
79
90
|
register(channel) {
|
|
80
|
-
if (this.
|
|
91
|
+
if (this.aborted) return;
|
|
92
|
+
if (this.has(channel.id)) throw new Error(`FunnelChannelSupervisor: channel id already registered: ${channel.id}`);
|
|
81
93
|
if (!this.started) {
|
|
82
94
|
this.pending.set(channel.id, channel);
|
|
83
95
|
return;
|
|
84
96
|
}
|
|
85
|
-
this.openChannel(channel)
|
|
97
|
+
const open = this.openChannel(channel).finally(() => {
|
|
98
|
+
this.opening.delete(channel.id);
|
|
99
|
+
});
|
|
100
|
+
this.opening.set(channel.id, open);
|
|
86
101
|
}
|
|
102
|
+
/** Waits for an in-flight post-start open of the same id before removing */
|
|
87
103
|
async unregister(id) {
|
|
88
104
|
this.pending.delete(id);
|
|
105
|
+
const open = this.opening.get(id);
|
|
106
|
+
if (open) await open;
|
|
89
107
|
const entry = this.registered.get(id);
|
|
90
108
|
if (!entry) return;
|
|
91
109
|
this.registered.delete(id);
|
|
@@ -93,7 +111,7 @@ var FunnelChannelSupervisor = class {
|
|
|
93
111
|
await this.confluence.remove(id);
|
|
94
112
|
}
|
|
95
113
|
async start() {
|
|
96
|
-
if (this.started) return;
|
|
114
|
+
if (this.started || this.aborted) return;
|
|
97
115
|
this.started = true;
|
|
98
116
|
const channels = [...this.pending.values()];
|
|
99
117
|
this.pending.clear();
|
|
@@ -101,32 +119,27 @@ var FunnelChannelSupervisor = class {
|
|
|
101
119
|
}
|
|
102
120
|
async stop() {
|
|
103
121
|
this.started = false;
|
|
122
|
+
this.pending.clear();
|
|
123
|
+
await Promise.all(this.opening.values());
|
|
104
124
|
for (const entry of this.registered.values()) entry.abortController.abort();
|
|
105
125
|
this.registered.clear();
|
|
106
|
-
this.pending.clear();
|
|
107
126
|
await this.confluence.closeAll();
|
|
108
127
|
}
|
|
109
128
|
ids() {
|
|
110
129
|
return [...this.registered.keys()];
|
|
111
130
|
}
|
|
112
131
|
has(id) {
|
|
113
|
-
return this.registered.has(id) || this.pending.has(id);
|
|
132
|
+
return this.registered.has(id) || this.pending.has(id) || this.opening.has(id);
|
|
114
133
|
}
|
|
134
|
+
/** Never rejects: build/add failures are logged and the channel is skipped */
|
|
115
135
|
async openChannel(channel) {
|
|
116
|
-
const channelDir = join(this.props.dir, "channels", channel.id);
|
|
117
136
|
const abortController = new AbortController();
|
|
118
|
-
const runtime = await channel.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
fs: this.props.fs,
|
|
125
|
-
statePersister: createChannelStatePersisterFactory({
|
|
126
|
-
fs: this.props.fs,
|
|
127
|
-
channelDir
|
|
128
|
-
})
|
|
129
|
-
});
|
|
137
|
+
const runtime = await this.buildRuntime(channel, abortController.signal);
|
|
138
|
+
if (runtime instanceof Error) {
|
|
139
|
+
this.props.logger.error(`channel "${channel.id}" failed to build`, { error: runtime.message });
|
|
140
|
+
abortController.abort();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
130
143
|
const result = await this.confluence.add(channel.id, runtime.sources);
|
|
131
144
|
if (result instanceof Error) {
|
|
132
145
|
this.props.logger.error(`channel "${channel.id}" failed to start`, { error: result.message });
|
|
@@ -139,6 +152,25 @@ var FunnelChannelSupervisor = class {
|
|
|
139
152
|
abortController
|
|
140
153
|
});
|
|
141
154
|
}
|
|
155
|
+
async buildRuntime(channel, signal) {
|
|
156
|
+
const channelDir = join(this.props.dir, "channels", channel.id);
|
|
157
|
+
try {
|
|
158
|
+
return await channel.build({
|
|
159
|
+
channelId: channel.id,
|
|
160
|
+
channelName: channel.name ?? channel.id,
|
|
161
|
+
signal,
|
|
162
|
+
logger: this.props.logger,
|
|
163
|
+
clock: this.props.clock,
|
|
164
|
+
fs: this.props.fs,
|
|
165
|
+
statePersister: createChannelStatePersisterFactory({
|
|
166
|
+
fs: this.props.fs,
|
|
167
|
+
channelDir
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
142
174
|
handleItem(item) {
|
|
143
175
|
if (item.kind !== "event") return;
|
|
144
176
|
const entry = this.registered.get(item.groupId);
|
|
@@ -147,26 +179,28 @@ var FunnelChannelSupervisor = class {
|
|
|
147
179
|
if (payload === null) return;
|
|
148
180
|
this.props.broadcaster.broadcast(payload.content, payload.meta);
|
|
149
181
|
}
|
|
182
|
+
/** Never throws: a throwing user transform is logged and the event dropped */
|
|
150
183
|
transformToPayload(entry, event) {
|
|
151
184
|
const transform = entry.runtime.transform;
|
|
152
185
|
if (transform === void 0) return {
|
|
153
186
|
content: JSON.stringify(event.data),
|
|
154
187
|
meta: event.meta
|
|
155
188
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
try {
|
|
190
|
+
return transform(event);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
this.props.logger.error(`channel "${entry.channel.id}" transform threw`, { error: message });
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
162
196
|
}
|
|
163
197
|
};
|
|
164
198
|
//#endregion
|
|
165
199
|
//#region lib/engine/channel/time-channel.ts
|
|
166
200
|
/**
|
|
167
|
-
*
|
|
201
|
+
* Factory for a single-cron-entry time channel.
|
|
168
202
|
*
|
|
169
|
-
*
|
|
203
|
+
* Example (inta jiho):
|
|
170
204
|
* ```ts
|
|
171
205
|
* const jiho = timeChannel({
|
|
172
206
|
* id: "inta-jiho",
|
|
@@ -178,8 +212,9 @@ var FunnelChannelSupervisor = class {
|
|
|
178
212
|
* })
|
|
179
213
|
* ```
|
|
180
214
|
*
|
|
181
|
-
* `persist: true` (
|
|
182
|
-
*
|
|
215
|
+
* With `persist: true` (default), `lastFiredAt` is kept in `<channelDir>/time.json`
|
|
216
|
+
* and missed ticks are recovered on startup according to `catchupPolicy`
|
|
217
|
+
* (default lastOnly)
|
|
183
218
|
*/
|
|
184
219
|
function timeChannel(options) {
|
|
185
220
|
const persist = options.persist ?? true;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { n as FunnelClock, t as FunnelLogger } from "./logger-
|
|
2
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
3
|
-
import { n as ChannelDeliveryMode, t as ChannelConfig } from "./settings-schema-
|
|
4
|
-
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-
|
|
5
|
-
import { A as FunnelConnectorListener, F as BaseConnectorConfig, M as CallInput, N as FunnelConnectorAdapter, c as FunnelHttpClient, g as ConnectorDiagnosticLog, n as ConnectorBuildContext, o as ConnectorOperationContext, r as ConnectorDescriptor, s as ConnectorUpdateContext, z as FunnelProcessRunner } from "./connector-descriptor-
|
|
6
|
-
|
|
1
|
+
import { n as FunnelClock, t as FunnelLogger } from "./logger-mMZvblTx.js";
|
|
2
|
+
import { n as FunnelFileSystem } from "./file-system-BNtl997T.js";
|
|
3
|
+
import { n as ChannelDeliveryMode, t as ChannelConfig } from "./settings-schema-B-CKLuST.js";
|
|
4
|
+
import { n as FunnelIdGenerator, t as FunnelSettingsReader } from "./settings-reader-CyTpbAre.js";
|
|
5
|
+
import { A as FunnelConnectorListener, F as BaseConnectorConfig, M as CallInput, N as FunnelConnectorAdapter, c as FunnelHttpClient, g as ConnectorDiagnosticLog, n as ConnectorBuildContext, o as ConnectorOperationContext, r as ConnectorDescriptor, s as ConnectorUpdateContext, z as FunnelProcessRunner } from "./connector-descriptor-F75ZI8Fh.js";
|
|
7
6
|
//#region lib/engine/connectors/connector-registry.d.ts
|
|
8
7
|
type Deps$1 = {
|
|
9
8
|
/** Connector types this funnel handles. Each is a self-describing descriptor;
|
package/dist/claude.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { n as FunnelFileSystem } from "./file-system-
|
|
2
|
-
import { a as ProcessGuard, c as ChannelResolver, i as SessionStore, n as FunnelClaude, o as McpInstaller, r as LaunchOptions, s as GatewayController, t as FunnelFileProcessGuard } from "./file-process-guard-
|
|
3
|
-
import { r as ConnectorDescriptor } from "./connector-descriptor-
|
|
4
|
-
import { t as FunnelProfiles } from "./profiles-
|
|
5
|
-
|
|
1
|
+
import { n as FunnelFileSystem } from "./file-system-BNtl997T.js";
|
|
2
|
+
import { a as ProcessGuard, c as ChannelResolver, i as SessionStore, n as FunnelClaude, o as McpInstaller, r as LaunchOptions, s as GatewayController, t as FunnelFileProcessGuard } from "./file-process-guard-DAYe9Ndt.js";
|
|
3
|
+
import { r as ConnectorDescriptor } from "./connector-descriptor-F75ZI8Fh.js";
|
|
4
|
+
import { t as FunnelProfiles } from "./profiles-CePRnv3F.js";
|
|
6
5
|
//#region lib/engine/mcp/mcp.d.ts
|
|
7
6
|
declare const FUNNEL_MCP_COMMAND = "bun";
|
|
8
7
|
declare const FUNNEL_MCP_ARGS: string[];
|
|
@@ -35,9 +34,13 @@ declare class FunnelMcp {
|
|
|
35
34
|
//#endregion
|
|
36
35
|
//#region lib/engine/mcp/channel-server.d.ts
|
|
37
36
|
type ChannelServerOptions = {
|
|
38
|
-
/** Funnel home directory (settings.json + gateway.token). Defaults to ~/.funnel. */
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
/** Funnel home directory (settings.json + gateway.token). Defaults to ~/.funnel. */
|
|
38
|
+
dir?: string;
|
|
39
|
+
/** Gateway base URL. Defaults to `$FUNNEL_GATEWAY_URL` or `http://127.0.0.1:<port>`. */
|
|
40
|
+
gatewayUrl?: string;
|
|
41
|
+
/** Channel id to subscribe to. Defaults to `$FUNNEL_CHANNEL_ID`. */
|
|
42
|
+
channelId?: string;
|
|
43
|
+
/** Auth token. Defaults to `$FUNNEL_GATEWAY_TOKEN` then `<dir>/gateway.token`. */
|
|
41
44
|
token?: string;
|
|
42
45
|
/** Connector descriptors whose tools this MCP server exposes. Defaults to the
|
|
43
46
|
* four built-in connectors; the tool-exposed subset is derived from them. */
|
package/dist/claude.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { n as gatewayLoopbackUrl, t as loopbackFetch } from "./loopback-fetch-
|
|
2
|
-
import { f as settingsSchema, s as resolveFunnelPort } from "./settings-store-
|
|
3
|
-
import { a as FunnelMcp, i as FUNNEL_MCP_NAME, n as FUNNEL_MCP_ARGS, o as FunnelFileProcessGuard, r as FUNNEL_MCP_COMMAND, s as FunnelClaude, t as renderYaml } from "./yaml-render-
|
|
4
|
-
import { t as
|
|
5
|
-
import { t as
|
|
6
|
-
import { t as
|
|
7
|
-
import { t as discordConnector } from "./discord-connector-
|
|
8
|
-
import { t as ghConnector } from "./gh-connector-
|
|
9
|
-
import { t as scheduleConnector } from "./schedule-connector-
|
|
10
|
-
import { t as slackConnector } from "./slack-connector-
|
|
1
|
+
import { n as gatewayLoopbackUrl, t as loopbackFetch } from "./loopback-fetch-HP-bo9Kv.js";
|
|
2
|
+
import { f as settingsSchema, s as resolveFunnelPort } from "./settings-store-ZFWBhOnp.js";
|
|
3
|
+
import { a as FunnelMcp, i as FUNNEL_MCP_NAME, n as FUNNEL_MCP_ARGS, o as FunnelFileProcessGuard, r as FUNNEL_MCP_COMMAND, s as FunnelClaude, t as renderYaml } from "./yaml-render-DxxRQT-V.js";
|
|
4
|
+
import { t as FunnelProfiles } from "./profiles-KpN7_gBw.js";
|
|
5
|
+
import { t as errorMessageOf } from "./error-message-of-BG4ezZ_s.js";
|
|
6
|
+
import { t as FunnelDocs } from "./funnel-docs-BFcqITFA.js";
|
|
7
|
+
import { t as discordConnector } from "./discord-connector-CdjwPoty.js";
|
|
8
|
+
import { t as ghConnector } from "./gh-connector-D-iyjl-d.js";
|
|
9
|
+
import { t as scheduleConnector } from "./schedule-connector-teeDMHUe.js";
|
|
10
|
+
import { t as slackConnector } from "./slack-connector-BE0KAjCs.js";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
13
|
import { homedir } from "node:os";
|
|
@@ -506,11 +506,14 @@ const MAX_RECONNECT_DELAY = 1e4;
|
|
|
506
506
|
* Reconnects with exponential backoff and replays missed events via `?since=<offset>`.
|
|
507
507
|
*/
|
|
508
508
|
var FunnelChannelSubscriber = class {
|
|
509
|
+
props;
|
|
509
510
|
state = {
|
|
510
511
|
reconnectDelay: RECONNECT_DELAY,
|
|
511
512
|
lastOffset: 0,
|
|
512
513
|
isStarted: false,
|
|
513
|
-
hasPendingReconnect: false
|
|
514
|
+
hasPendingReconnect: false,
|
|
515
|
+
hasDeliveryFailure: false,
|
|
516
|
+
messageQueue: Promise.resolve()
|
|
514
517
|
};
|
|
515
518
|
constructor(props) {
|
|
516
519
|
this.props = props;
|
|
@@ -529,14 +532,17 @@ var FunnelChannelSubscriber = class {
|
|
|
529
532
|
this.state.reconnectDelay = RECONNECT_DELAY;
|
|
530
533
|
process.stderr.write(`funnel: connected (${wsUrl})\n`);
|
|
531
534
|
});
|
|
532
|
-
ws.addEventListener("message", (event) => this.
|
|
535
|
+
ws.addEventListener("message", (event) => this.enqueueMessage(event, ws));
|
|
533
536
|
ws.addEventListener("close", () => {
|
|
534
537
|
if (this.state.hasPendingReconnect) return;
|
|
535
538
|
this.state.hasPendingReconnect = true;
|
|
536
539
|
process.stderr.write(`funnel: disconnected, reconnecting in ${this.state.reconnectDelay}ms\n`);
|
|
537
540
|
setTimeout(() => {
|
|
538
|
-
this.state.
|
|
539
|
-
|
|
541
|
+
this.state.messageQueue.then(() => {
|
|
542
|
+
this.state.hasPendingReconnect = false;
|
|
543
|
+
this.state.hasDeliveryFailure = false;
|
|
544
|
+
this.connect();
|
|
545
|
+
});
|
|
540
546
|
}, this.state.reconnectDelay);
|
|
541
547
|
this.state.reconnectDelay = Math.min(this.state.reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
|
542
548
|
});
|
|
@@ -545,12 +551,19 @@ var FunnelChannelSubscriber = class {
|
|
|
545
551
|
process.stderr.write(`funnel: ws error: ${reason}\n`);
|
|
546
552
|
});
|
|
547
553
|
}
|
|
554
|
+
enqueueMessage(event, socket) {
|
|
555
|
+
this.state.messageQueue = this.state.messageQueue.then(async () => {
|
|
556
|
+
if (this.state.hasDeliveryFailure) return;
|
|
557
|
+
if (await this.handleMessage(event)) return;
|
|
558
|
+
this.state.hasDeliveryFailure = true;
|
|
559
|
+
socket?.close();
|
|
560
|
+
});
|
|
561
|
+
}
|
|
548
562
|
async handleMessage(event) {
|
|
549
563
|
try {
|
|
550
564
|
const payload = JSON.parse(String(event.data));
|
|
551
565
|
const eventType = payload.meta?.event_type ?? "unknown";
|
|
552
566
|
const offset = typeof payload.offset === "number" ? payload.offset : null;
|
|
553
|
-
if (offset !== null && offset > this.state.lastOffset) this.state.lastOffset = offset;
|
|
554
567
|
process.stderr.write(`funnel: received event (${eventType})\n`);
|
|
555
568
|
await this.props.server.notification({
|
|
556
569
|
method: "notifications/claude/channel",
|
|
@@ -559,8 +572,11 @@ var FunnelChannelSubscriber = class {
|
|
|
559
572
|
meta: payload.meta
|
|
560
573
|
}
|
|
561
574
|
});
|
|
575
|
+
if (offset !== null && offset > this.state.lastOffset) this.state.lastOffset = offset;
|
|
576
|
+
return true;
|
|
562
577
|
} catch (error) {
|
|
563
578
|
process.stderr.write(`funnel: error handling ws message (offset=${this.state.lastOffset}): ${errorMessageOf(error)}\n`);
|
|
579
|
+
return false;
|
|
564
580
|
}
|
|
565
581
|
}
|
|
566
582
|
};
|