@interactive-inc/claude-funnel 0.8.0 → 0.8.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 +583 -656
- package/dist/cli/factory.d.ts +7 -0
- package/dist/cli/router/query-to-cli-args.d.ts +1 -0
- package/dist/cli/router/to-request.d.ts +5 -0
- package/dist/cli/router/validator.d.ts +5 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.d.ts +42 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.rename.$newName.d.ts +46 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.request.d.ts +54 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.add.$id.d.ts +66 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.d.ts +42 -0
- package/dist/cli/routes/channels.$channel.connectors.$connector.schedules.remove.$id.d.ts +46 -0
- package/dist/cli/routes/channels.$channel.connectors.add.$connector.d.ts +90 -0
- package/dist/cli/routes/channels.$channel.connectors.d.ts +38 -0
- package/dist/cli/routes/channels.$channel.connectors.remove.$connector.d.ts +42 -0
- package/dist/cli/routes/channels.$channel.connectors.set.$connector.d.ts +62 -0
- package/dist/cli/routes/channels.$channel.d.ts +38 -0
- package/dist/cli/routes/channels.$channel.rename.$newName.d.ts +42 -0
- package/dist/cli/routes/channels.$channel.set.delivery.$mode.d.ts +28 -0
- package/dist/cli/routes/channels.add.$channel.d.ts +46 -0
- package/dist/cli/routes/channels.d.ts +16 -0
- package/dist/cli/routes/channels.remove.$channel.d.ts +38 -0
- package/dist/cli/routes/claude.d.ts +32 -0
- package/dist/cli/routes/gateway.d.ts +20 -0
- package/dist/cli/routes/gateway.listeners.d.ts +17 -0
- package/dist/cli/routes/gateway.logs.d.ts +24 -0
- package/dist/cli/routes/gateway.restart.d.ts +24 -0
- package/dist/cli/routes/gateway.run.d.ts +24 -0
- package/dist/cli/routes/gateway.start.d.ts +24 -0
- package/dist/cli/routes/gateway.status.d.ts +13 -0
- package/dist/cli/routes/gateway.stop.d.ts +16 -0
- package/dist/cli/routes/index.d.ts +1222 -0
- package/dist/cli/routes/profiles.$profile.as-default.d.ts +38 -0
- package/dist/cli/routes/profiles.$profile.rename.$newName.d.ts +42 -0
- package/dist/cli/routes/profiles.$profile.run.d.ts +46 -0
- package/dist/cli/routes/profiles.add.$profile.d.ts +54 -0
- package/dist/cli/routes/profiles.d.ts +16 -0
- package/dist/cli/routes/profiles.remove.$profile.d.ts +38 -0
- package/dist/cli/routes/profiles.set.$profile.d.ts +54 -0
- package/dist/cli/routes/status.d.ts +16 -0
- package/dist/cli/routes/update.d.ts +16 -0
- package/dist/connectors/connector-adapter.d.ts +8 -0
- package/dist/connectors/connector-config-schema.d.ts +43 -0
- package/dist/connectors/connector-factory.d.ts +32 -0
- package/dist/connectors/connector-listener.d.ts +17 -0
- package/dist/connectors/discord-adapter.d.ts +14 -0
- package/dist/connectors/discord-connector-schema.d.ts +10 -0
- package/dist/connectors/discord-event-processor.d.ts +26 -0
- package/dist/connectors/discord-listener.d.ts +17 -0
- package/dist/connectors/gh-adapter.d.ts +11 -0
- package/dist/connectors/gh-connector-schema.d.ts +10 -0
- package/dist/connectors/gh-listener.d.ts +26 -0
- package/dist/connectors/match-cron.d.ts +1 -0
- package/dist/connectors/schedule-connector-schema.d.ts +45 -0
- package/dist/connectors/schedule-listener.d.ts +30 -0
- package/dist/connectors/schedule-state-store.d.ts +19 -0
- package/dist/connectors/slack-adapter.d.ts +15 -0
- package/dist/connectors/slack-connector-schema.d.ts +11 -0
- package/dist/connectors/slack-event-processor.d.ts +27 -0
- package/dist/connectors/slack-listener.d.ts +17 -0
- package/dist/engine/channels/channels.d.ts +106 -0
- package/dist/engine/claude/claude.d.ts +49 -0
- package/dist/engine/claude/gateway-controller.d.ts +6 -0
- package/dist/engine/fs/file-system.d.ts +24 -0
- package/dist/engine/fs/memory-file-system.d.ts +31 -0
- package/dist/engine/fs/node-file-system.d.ts +15 -0
- package/dist/engine/http/http-client.d.ts +15 -0
- package/dist/engine/http/memory-http-client.d.ts +12 -0
- package/dist/engine/http/node-http-client.d.ts +5 -0
- package/dist/engine/id/id-generator.d.ts +7 -0
- package/dist/engine/id/memory-id-generator.d.ts +11 -0
- package/dist/engine/id/node-id-generator.d.ts +4 -0
- package/dist/engine/logger/logger.d.ts +11 -0
- package/dist/engine/logger/memory-logger.d.ts +14 -0
- package/dist/engine/logger/node-logger.d.ts +15 -0
- package/dist/engine/logger/noop-logger.d.ts +7 -0
- package/dist/engine/mcp/channel-server.d.ts +1 -0
- package/dist/engine/mcp/mcp.d.ts +22 -0
- package/dist/engine/process/memory-process-runner.d.ts +43 -0
- package/dist/engine/process/node-process-runner.d.ts +9 -0
- package/dist/engine/process/process-runner.d.ts +29 -0
- package/dist/engine/profiles/profile-channel-checker.d.ts +7 -0
- package/dist/engine/profiles/profiles.d.ts +31 -0
- package/dist/engine/settings/mock-settings-reader.d.ts +9 -0
- package/dist/engine/settings/settings-reader.d.ts +5 -0
- package/dist/engine/settings/settings-schema.d.ts +132 -0
- package/dist/engine/settings/settings-store.d.ts +18 -0
- package/dist/engine/time/clock.d.ts +9 -0
- package/dist/engine/time/memory-clock.d.ts +12 -0
- package/dist/engine/time/node-clock.d.ts +4 -0
- package/dist/funnel.d.ts +95 -0
- package/dist/gateway/auth-middleware.d.ts +14 -0
- package/dist/gateway/broadcaster.d.ts +122 -0
- package/dist/gateway/daemon.d.ts +2 -0
- package/dist/gateway/daemon.js +192 -220
- package/dist/gateway/factory.d.ts +7 -0
- package/dist/gateway/funnel-event-store.d.ts +81 -0
- package/dist/gateway/gateway-server.d.ts +94 -0
- package/dist/gateway/gateway-token.d.ts +33 -0
- package/dist/gateway/gateway.d.ts +58 -0
- package/dist/gateway/kill-competing-slack-gateways.d.ts +9 -0
- package/dist/gateway/listener-supervisor.d.ts +85 -0
- package/dist/gateway/listeners-client.d.ts +53 -0
- package/dist/gateway/resolve-daemon-script.d.ts +11 -0
- package/dist/gateway/routes/channels.connectors.call.d.ts +41 -0
- package/dist/gateway/routes/health.d.ts +17 -0
- package/dist/gateway/routes/index.d.ts +209 -0
- package/dist/gateway/routes/listeners.list.d.ts +14 -0
- package/dist/gateway/routes/listeners.restart.d.ts +34 -0
- package/dist/gateway/routes/listeners.start.d.ts +34 -0
- package/dist/gateway/routes/listeners.stop.d.ts +34 -0
- package/dist/gateway/routes/route-deps.d.ts +10 -0
- package/dist/gateway/routes/status.d.ts +30 -0
- package/dist/gateway/routes/validator.d.ts +19 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +3575 -0
- package/dist/logger/leuco-human-file-writer.d.ts +33 -0
- package/dist/logger/leuco-human-logger.d.ts +46 -0
- package/dist/logger/leuco-human-record.d.ts +15 -0
- package/dist/logger/leuco-human-stdout-writer.d.ts +20 -0
- package/dist/logger/leuco-human-writer.d.ts +13 -0
- package/dist/logger/leuco-logger-memory-sink.d.ts +33 -0
- package/dist/logger/leuco-logger-record.d.ts +13 -0
- package/dist/logger/leuco-logger-sink.d.ts +34 -0
- package/dist/logger/leuco-logger-sqlite-sink.d.ts +102 -0
- package/dist/logger/leuco-logger.d.ts +56 -0
- package/lib/index.ts +2 -0
- package/package.json +14 -9
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ReplayableEvent } from "./broadcaster";
|
|
3
|
+
/**
|
|
4
|
+
* Replayable event payload persisted by the gateway. Domain events the
|
|
5
|
+
* broadcaster emits to WS clients land here so reconnects across daemon
|
|
6
|
+
* restarts can be served from disk. System events (gateway start, channel
|
|
7
|
+
* connected, etc.) are routed to `FunnelLogger` instead — they never go
|
|
8
|
+
* through this store, which keeps the seq space clean for replay.
|
|
9
|
+
*/
|
|
10
|
+
export declare const funnelEventSchema: z.ZodObject<{
|
|
11
|
+
type: z.ZodString;
|
|
12
|
+
content: z.ZodString;
|
|
13
|
+
channel_id: z.ZodNullable<z.ZodString>;
|
|
14
|
+
connector_id: z.ZodNullable<z.ZodString>;
|
|
15
|
+
meta: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type FunnelEvent = z.infer<typeof funnelEventSchema>;
|
|
18
|
+
type Props = {
|
|
19
|
+
/** SQLite database file path. Created on first write. ":memory:" for tests. */
|
|
20
|
+
path: string;
|
|
21
|
+
/** Override for tests. Defaults to `Date.now`. */
|
|
22
|
+
now?: () => number;
|
|
23
|
+
/** Optional row cap. Pruned on every insert. */
|
|
24
|
+
maxRows?: number;
|
|
25
|
+
/** Optional age cap in ms. Pruned on every insert. */
|
|
26
|
+
maxAgeMs?: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* SQLite-backed event store. One indexed table holds every broadcaster
|
|
30
|
+
* event with `channel_id` and `connector_id` as dedicated columns, so
|
|
31
|
+
* per-channel and per-connector replay is an indexed range scan.
|
|
32
|
+
*
|
|
33
|
+
* Concurrency: `seq` is `INTEGER PRIMARY KEY`, so SQLite assigns it
|
|
34
|
+
* atomically. The broadcaster owns its own offset counter at runtime
|
|
35
|
+
* (seeded from `findMaxOffset()` at startup); each broadcaster event
|
|
36
|
+
* flows in here via `record()` with that pre-assigned offset, which the
|
|
37
|
+
* sink stores via `write()` — PK uniqueness catches double-emit bugs.
|
|
38
|
+
*
|
|
39
|
+
* System events (gateway lifecycle, channel connect/disconnect, etc.) do
|
|
40
|
+
* NOT go through this store. They are diagnostic only and live in
|
|
41
|
+
* `FunnelLogger`'s file so the seq space here stays exclusive to
|
|
42
|
+
* broadcaster traffic. This is what makes the broadcaster's seq seeding
|
|
43
|
+
* (`getMaxSeq()` at startup) correct without per-event coordination.
|
|
44
|
+
*/
|
|
45
|
+
export declare class FunnelEventStore {
|
|
46
|
+
private readonly sink;
|
|
47
|
+
private readonly now;
|
|
48
|
+
constructor(props: Props);
|
|
49
|
+
/**
|
|
50
|
+
* Persist a broadcaster-driven event with its assigned offset. Caller
|
|
51
|
+
* (the gateway-server) supplies the offset from `broadcaster.broadcast()`
|
|
52
|
+
* so this store and the broadcaster's in-memory ring stay aligned.
|
|
53
|
+
*/
|
|
54
|
+
record(props: {
|
|
55
|
+
content: string;
|
|
56
|
+
channelId: string | null;
|
|
57
|
+
connectorId: string | null;
|
|
58
|
+
meta: Record<string, string> | null;
|
|
59
|
+
offset: number;
|
|
60
|
+
}): void;
|
|
61
|
+
/**
|
|
62
|
+
* Returns events with offset > since. Filtering by channel/connector is
|
|
63
|
+
* the broadcaster's responsibility (it knows the client's subscription),
|
|
64
|
+
* so this returns the full slice and lets the caller filter.
|
|
65
|
+
*/
|
|
66
|
+
loadSince(since: number): ReplayableEvent[];
|
|
67
|
+
/**
|
|
68
|
+
* Returns events for one channel (and optionally one connector). Used
|
|
69
|
+
* by the gateway logs CLI for scoped queries. Channel/connector filters
|
|
70
|
+
* are indexed columns, so this is an indexed range scan.
|
|
71
|
+
*/
|
|
72
|
+
loadForChannel(props: {
|
|
73
|
+
channelId: string;
|
|
74
|
+
connectorId?: string;
|
|
75
|
+
sinceSeq?: number;
|
|
76
|
+
limit?: number;
|
|
77
|
+
}): ReplayableEvent[];
|
|
78
|
+
findMaxOffset(): number;
|
|
79
|
+
close(): void;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Server } from "bun";
|
|
2
|
+
import type { FunnelChannels } from "../engine/channels/channels";
|
|
3
|
+
import { FunnelBroadcaster } from "./broadcaster";
|
|
4
|
+
import { FunnelEventStore } from "./funnel-event-store";
|
|
5
|
+
import { FunnelListenerSupervisor } from "./listener-supervisor";
|
|
6
|
+
import { FunnelLogger } from "../engine/logger/logger";
|
|
7
|
+
import type { FunnelProcessRunner } from "../engine/process/process-runner";
|
|
8
|
+
import type { FunnelSettingsReader } from "../engine/settings/settings-reader";
|
|
9
|
+
import type { FunnelClock } from "../engine/time/clock";
|
|
10
|
+
type Deps = {
|
|
11
|
+
channels: FunnelChannels;
|
|
12
|
+
settings: FunnelSettingsReader;
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Directory holding the SQLite event store. The DB file lives at `<logDir>/events.db`. */
|
|
15
|
+
logDir?: string;
|
|
16
|
+
process?: FunnelProcessRunner;
|
|
17
|
+
clock?: FunnelClock;
|
|
18
|
+
logger?: FunnelLogger;
|
|
19
|
+
selfPid?: number;
|
|
20
|
+
killCompetingSlack?: boolean;
|
|
21
|
+
/** Bearer token required for `/listeners*`, `/status`, and `/ws`. Empty string disables auth (tests only). */
|
|
22
|
+
token?: string;
|
|
23
|
+
};
|
|
24
|
+
type WsData = {
|
|
25
|
+
/** Stable channel id (uuid) the client subscribed to. "" for tap-all clients. */
|
|
26
|
+
channel: string;
|
|
27
|
+
/** Resolved channel name (for log readability). null for tap-all or unknown. */
|
|
28
|
+
channelName: string | null;
|
|
29
|
+
/** Connector names belonging to that channel; used by tap-all replay filtering. */
|
|
30
|
+
connectors: string[];
|
|
31
|
+
tapAll?: boolean;
|
|
32
|
+
/** Routing mode for this channel; resolved at upgrade time from settings. */
|
|
33
|
+
delivery: "fanout" | "exclusive";
|
|
34
|
+
/** Replay any events with offset strictly greater than this on open, then resume the live stream. */
|
|
35
|
+
since?: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* In-process gateway: runs `Bun.serve` (HTTP + WebSocket /ws), boots connector
|
|
39
|
+
* listeners through `FunnelListenerSupervisor`, fans events out via
|
|
40
|
+
* `FunnelBroadcaster`, and persists them via `FunnelEventStore` (SQLite).
|
|
41
|
+
* System events (gateway lifecycle, connect/disconnect) flow to `FunnelLogger`
|
|
42
|
+
* instead — keeping the SQLite seq space exclusive to broadcaster traffic so
|
|
43
|
+
* the broadcaster's offset counter and `getMaxSeq()` stay aligned without
|
|
44
|
+
* per-event coordination. Exposes `/listeners` HTTP for runtime
|
|
45
|
+
* start/stop/restart of individual connectors.
|
|
46
|
+
*/
|
|
47
|
+
export declare class FunnelGatewayServer {
|
|
48
|
+
private readonly channels;
|
|
49
|
+
private readonly settings;
|
|
50
|
+
private readonly port;
|
|
51
|
+
private readonly logDir;
|
|
52
|
+
private readonly process?;
|
|
53
|
+
private readonly logger;
|
|
54
|
+
private readonly selfPid;
|
|
55
|
+
private readonly killCompetingSlack;
|
|
56
|
+
private readonly token;
|
|
57
|
+
private readonly broadcaster;
|
|
58
|
+
private readonly eventStore;
|
|
59
|
+
private readonly supervisor;
|
|
60
|
+
private readonly nowMs;
|
|
61
|
+
private startedAt;
|
|
62
|
+
private server;
|
|
63
|
+
constructor(deps: Deps);
|
|
64
|
+
start(): Promise<Server<WsData>>;
|
|
65
|
+
stop(): Promise<void>;
|
|
66
|
+
getStatus(): {
|
|
67
|
+
clients: number;
|
|
68
|
+
channels: {
|
|
69
|
+
channel: string;
|
|
70
|
+
connectors: string[];
|
|
71
|
+
}[];
|
|
72
|
+
};
|
|
73
|
+
getBroadcaster(): FunnelBroadcaster;
|
|
74
|
+
getSupervisor(): FunnelListenerSupervisor;
|
|
75
|
+
getEventStore(): FunnelEventStore;
|
|
76
|
+
private handleFetch;
|
|
77
|
+
private handleWsOpen;
|
|
78
|
+
private handleWsClose;
|
|
79
|
+
private logServerStarted;
|
|
80
|
+
private buildApp;
|
|
81
|
+
/**
|
|
82
|
+
* Reads the bearer token from the WebSocket upgrade request. Accepts:
|
|
83
|
+
* - `Sec-WebSocket-Protocol: funnel.token.<value>` (preferred — header, never logged in URLs)
|
|
84
|
+
* - `Authorization: Bearer <value>` (also header-based)
|
|
85
|
+
* Returns true on a constant-time match against the daemon token.
|
|
86
|
+
*/
|
|
87
|
+
private tokenMatchesUpgrade;
|
|
88
|
+
private resolveChannel;
|
|
89
|
+
private bootListeners;
|
|
90
|
+
private notify;
|
|
91
|
+
private lookupChannelId;
|
|
92
|
+
private lookupConnectorId;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FunnelFileSystem } from "../engine/fs/file-system";
|
|
2
|
+
type Deps = {
|
|
3
|
+
fs?: FunnelFileSystem;
|
|
4
|
+
dir?: string;
|
|
5
|
+
generate?: () => string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Reads / generates the gateway daemon token used to authenticate
|
|
9
|
+
* `/listeners*`, `/status`, and `/ws` connections.
|
|
10
|
+
*
|
|
11
|
+
* Token file: `<dir>/gateway.token` (default `~/.funnel/gateway.token`),
|
|
12
|
+
* written with mode 0600. Clients on the same machine as the daemon read
|
|
13
|
+
* the file directly; the token never leaves the user's home directory.
|
|
14
|
+
*/
|
|
15
|
+
export declare class FunnelGatewayToken {
|
|
16
|
+
private readonly fs;
|
|
17
|
+
private readonly path;
|
|
18
|
+
private readonly generate;
|
|
19
|
+
constructor(deps?: Deps);
|
|
20
|
+
read(): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the existing token or, if missing, generates one and writes it with mode 0600.
|
|
23
|
+
*
|
|
24
|
+
* NOTE: not atomic — two concurrent `ensure()` calls (e.g., `fnl gateway start` racing
|
|
25
|
+
* itself before the PID lock is acquired) could each generate independent tokens. The
|
|
26
|
+
* gateway PID file makes this practically a non-issue; if you need stronger guarantees,
|
|
27
|
+
* take a file lock around this call externally.
|
|
28
|
+
*/
|
|
29
|
+
ensure(): string;
|
|
30
|
+
getPath(): string;
|
|
31
|
+
}
|
|
32
|
+
export declare const DEFAULT_GATEWAY_TOKEN_PATH: string;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FunnelFileSystem } from "../engine/fs/file-system";
|
|
2
|
+
import { FunnelProcessRunner } from "../engine/process/process-runner";
|
|
3
|
+
import { FunnelClock } from "../engine/time/clock";
|
|
4
|
+
type Deps = {
|
|
5
|
+
process?: FunnelProcessRunner;
|
|
6
|
+
fs?: FunnelFileSystem;
|
|
7
|
+
clock?: FunnelClock;
|
|
8
|
+
dir?: string;
|
|
9
|
+
tmpDir?: string;
|
|
10
|
+
port?: number;
|
|
11
|
+
sleep?: (ms: number) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Manages the gateway daemon as a separate process via PID file.
|
|
15
|
+
* Use `start()` to spawn `bun daemon.ts` in the background and `stop()` to
|
|
16
|
+
* terminate it. For an in-process gateway, use `Funnel.gatewayServer` instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare class FunnelGateway {
|
|
19
|
+
private readonly process;
|
|
20
|
+
private readonly fs;
|
|
21
|
+
private readonly clock;
|
|
22
|
+
private readonly pidFile;
|
|
23
|
+
private readonly logDir;
|
|
24
|
+
private readonly gatewayLog;
|
|
25
|
+
private readonly tmpDir;
|
|
26
|
+
private readonly port;
|
|
27
|
+
private readonly sleep;
|
|
28
|
+
constructor(deps?: Deps);
|
|
29
|
+
isRunning(): boolean;
|
|
30
|
+
getStatus(): {
|
|
31
|
+
running: boolean;
|
|
32
|
+
pid: number | null;
|
|
33
|
+
port: number;
|
|
34
|
+
};
|
|
35
|
+
start(options?: {
|
|
36
|
+
caffeinate?: boolean;
|
|
37
|
+
}): Promise<boolean>;
|
|
38
|
+
buildStartCommand(gatewayScript: string, options?: {
|
|
39
|
+
caffeinate?: boolean;
|
|
40
|
+
}): string;
|
|
41
|
+
stop(): Promise<boolean>;
|
|
42
|
+
restart(options?: {
|
|
43
|
+
onlyIfRunning?: boolean;
|
|
44
|
+
caffeinate?: boolean;
|
|
45
|
+
}): Promise<{
|
|
46
|
+
ok: boolean;
|
|
47
|
+
wasRunning: boolean;
|
|
48
|
+
stopped: boolean;
|
|
49
|
+
started: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
getLogDir(): string;
|
|
52
|
+
getGatewayLog(): string;
|
|
53
|
+
getPort(): number;
|
|
54
|
+
private readPid;
|
|
55
|
+
private removePid;
|
|
56
|
+
private isProcessAlive;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FunnelLogger } from "../engine/logger/logger";
|
|
2
|
+
import { FunnelProcessRunner } from "../engine/process/process-runner";
|
|
3
|
+
type Props = {
|
|
4
|
+
selfPid: number;
|
|
5
|
+
process?: FunnelProcessRunner;
|
|
6
|
+
logger?: FunnelLogger;
|
|
7
|
+
};
|
|
8
|
+
export declare const killCompetingSlackGateways: (props: Props) => Promise<number[]>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ConnectorConfig } from "../connectors/connector-config-schema";
|
|
2
|
+
import type { FunnelConnectorListener } from "../connectors/connector-listener";
|
|
3
|
+
import type { ChannelConnectorView } from "../engine/channels/channels";
|
|
4
|
+
import { FunnelLogger } from "../engine/logger/logger";
|
|
5
|
+
type ConnectorRegistry = {
|
|
6
|
+
listAllConnectors(): ChannelConnectorView[];
|
|
7
|
+
createListener(channelName: string, connectorName: string): {
|
|
8
|
+
config: ConnectorConfig;
|
|
9
|
+
channelId: string;
|
|
10
|
+
listener: FunnelConnectorListener;
|
|
11
|
+
} | null;
|
|
12
|
+
};
|
|
13
|
+
type SupervisorNotify = (channelName: string, connectorName: string, content: string, meta?: Record<string, string>) => Promise<void>;
|
|
14
|
+
type Deps = {
|
|
15
|
+
channels: ConnectorRegistry;
|
|
16
|
+
notify: SupervisorNotify;
|
|
17
|
+
logger?: FunnelLogger;
|
|
18
|
+
healthCheckIntervalMs?: number;
|
|
19
|
+
maxBackoffMs?: number;
|
|
20
|
+
sleep?: (ms: number) => Promise<void>;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
};
|
|
23
|
+
type ListenerEntryStatus = {
|
|
24
|
+
channelName: string;
|
|
25
|
+
channelId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
type: ConnectorConfig["type"];
|
|
28
|
+
alive: boolean;
|
|
29
|
+
events: number;
|
|
30
|
+
errors: number;
|
|
31
|
+
failureCount: number;
|
|
32
|
+
lastEventAt: string | null;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Owns the running listener instances and their lifecycle.
|
|
36
|
+
*
|
|
37
|
+
* Lives in the gateway process and is the only place that calls
|
|
38
|
+
* `listener.start()` / `listener.stop()`. Each entry is keyed by
|
|
39
|
+
* `${channelName}/${connectorName}` so the same connector name can exist in
|
|
40
|
+
* multiple channels without colliding.
|
|
41
|
+
*
|
|
42
|
+
* Periodically polls each running listener's `isAlive()` and auto-restarts
|
|
43
|
+
* dead listeners with exponential backoff (1s, 2s, 4s, ... capped). Resets
|
|
44
|
+
* the backoff counter on successful restart.
|
|
45
|
+
*/
|
|
46
|
+
export declare class FunnelListenerSupervisor {
|
|
47
|
+
private readonly channels;
|
|
48
|
+
private readonly notify;
|
|
49
|
+
private readonly logger;
|
|
50
|
+
private readonly running;
|
|
51
|
+
private readonly failureCounts;
|
|
52
|
+
private readonly stats;
|
|
53
|
+
private readonly healthCheckIntervalMs;
|
|
54
|
+
private readonly maxBackoffMs;
|
|
55
|
+
private readonly sleep;
|
|
56
|
+
private readonly now;
|
|
57
|
+
private healthCheckTimer;
|
|
58
|
+
private healthCheckInFlight;
|
|
59
|
+
constructor(deps: Deps);
|
|
60
|
+
static keyOf(channelName: string, connectorName: string): string;
|
|
61
|
+
isRunning(channelName: string, connectorName: string): boolean;
|
|
62
|
+
list(): ListenerEntryStatus[];
|
|
63
|
+
start(channelName: string, connectorName: string): Promise<{
|
|
64
|
+
ok: boolean;
|
|
65
|
+
reason?: string;
|
|
66
|
+
}>;
|
|
67
|
+
stop(channelName: string, connectorName: string): Promise<{
|
|
68
|
+
ok: boolean;
|
|
69
|
+
reason?: string;
|
|
70
|
+
}>;
|
|
71
|
+
restart(channelName: string, connectorName: string): Promise<{
|
|
72
|
+
ok: boolean;
|
|
73
|
+
reason?: string;
|
|
74
|
+
}>;
|
|
75
|
+
startAll(): Promise<void>;
|
|
76
|
+
stopAll(): Promise<void>;
|
|
77
|
+
private ensureStats;
|
|
78
|
+
private recordEvent;
|
|
79
|
+
private recordError;
|
|
80
|
+
private startHealthCheck;
|
|
81
|
+
private stopHealthCheck;
|
|
82
|
+
private runHealthCheck;
|
|
83
|
+
private recoverDead;
|
|
84
|
+
}
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
type Deps = {
|
|
3
|
+
port: number;
|
|
4
|
+
isDaemonRunning: () => boolean;
|
|
5
|
+
/** Returns the daemon's gateway token, or null if unavailable. Sent as `Authorization: Bearer`. */
|
|
6
|
+
getToken?: () => string | null;
|
|
7
|
+
};
|
|
8
|
+
declare const listenerEntrySchema: z.ZodObject<{
|
|
9
|
+
channelName: z.ZodString;
|
|
10
|
+
channelId: z.ZodString;
|
|
11
|
+
name: z.ZodString;
|
|
12
|
+
type: z.ZodString;
|
|
13
|
+
alive: z.ZodBoolean;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
export type ListenerEntry = z.infer<typeof listenerEntrySchema>;
|
|
16
|
+
export type ListenerOpResult = {
|
|
17
|
+
state: "ok";
|
|
18
|
+
} | {
|
|
19
|
+
state: "offline";
|
|
20
|
+
} | {
|
|
21
|
+
state: "error";
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
export type ListListenersResult = {
|
|
25
|
+
state: "ok";
|
|
26
|
+
listeners: ListenerEntry[];
|
|
27
|
+
} | {
|
|
28
|
+
state: "offline";
|
|
29
|
+
} | {
|
|
30
|
+
state: "error";
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* HTTP client for listener operations on a running gateway daemon.
|
|
35
|
+
*
|
|
36
|
+
* Returns `{ state: "offline" }` when the daemon isn't running so callers
|
|
37
|
+
* (CLI hot-reload paths) can treat that as a no-op without parsing strings.
|
|
38
|
+
* Pair this with `FunnelGateway` (process control) for the full picture.
|
|
39
|
+
*/
|
|
40
|
+
export declare class FunnelListenersClient {
|
|
41
|
+
private readonly port;
|
|
42
|
+
private readonly isDaemonRunning;
|
|
43
|
+
private readonly getToken;
|
|
44
|
+
constructor(deps: Deps);
|
|
45
|
+
list(): Promise<ListListenersResult>;
|
|
46
|
+
start(channelName: string, connectorName: string): Promise<ListenerOpResult>;
|
|
47
|
+
stop(channelName: string, connectorName: string): Promise<ListenerOpResult>;
|
|
48
|
+
restart(channelName: string, connectorName: string): Promise<ListenerOpResult>;
|
|
49
|
+
private path;
|
|
50
|
+
private authHeaders;
|
|
51
|
+
private call;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate the daemon entry script. Works in both dev (running from source)
|
|
3
|
+
* and built mode (bundled into dist/bin.js with daemon at dist/gateway/daemon.js).
|
|
4
|
+
*
|
|
5
|
+
* The candidates cover:
|
|
6
|
+
* 1. dev: this helper lives at lib/gateway/, so daemon.ts is its sibling
|
|
7
|
+
* 2. built sibling: dist/gateway/daemon.js if the helper itself ends up at dist/gateway/
|
|
8
|
+
* 3. bundled: when this helper is inlined into dist/bin.js, import.meta.dir is dist/,
|
|
9
|
+
* and daemon.js lives at dist/gateway/daemon.js
|
|
10
|
+
*/
|
|
11
|
+
export declare const resolveDaemonScript: () => string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /channels/:channel/connectors/:connector/call
|
|
3
|
+
*
|
|
4
|
+
* Generic adapter call. Used by the funnel MCP server (running in the Claude
|
|
5
|
+
* Code process) to send replies/reactions/etc. without spawning a CLI
|
|
6
|
+
* subprocess. Mirrors the CLI's `funnel channels <c> connectors <conn> request
|
|
7
|
+
* --method=...` but with a structured JSON body and no shell.
|
|
8
|
+
*/
|
|
9
|
+
export declare const channelsConnectorsCallHandler: [import("hono/types").H<import("hono").Env, string, {
|
|
10
|
+
in: {
|
|
11
|
+
param: {
|
|
12
|
+
channel: string;
|
|
13
|
+
connector: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
out: {
|
|
17
|
+
param: {
|
|
18
|
+
channel: string;
|
|
19
|
+
connector: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}, import("hono").TypedResponse<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
reason: string;
|
|
25
|
+
}, 400, "json">>, import("hono/types").H<import("../factory").Env, string, {
|
|
26
|
+
in: {
|
|
27
|
+
param: {
|
|
28
|
+
channel: string;
|
|
29
|
+
connector: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
out: {
|
|
33
|
+
param: {
|
|
34
|
+
channel: string;
|
|
35
|
+
connector: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}, Promise<Response & import("hono").TypedResponse<{
|
|
39
|
+
ok: true;
|
|
40
|
+
result: import("hono/utils/types").JSONValue;
|
|
41
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">>>];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** GET /health — liveness + listener registry snapshot. */
|
|
2
|
+
export declare const healthHandler: [import("hono/types").H<import("../factory").Env, string, {}, Response & import("hono").TypedResponse<{
|
|
3
|
+
ok: true;
|
|
4
|
+
pid: number;
|
|
5
|
+
clients: number;
|
|
6
|
+
listeners: {
|
|
7
|
+
channelName: string;
|
|
8
|
+
channelId: string;
|
|
9
|
+
name: string;
|
|
10
|
+
type: import("../..").ConnectorConfig["type"];
|
|
11
|
+
alive: boolean;
|
|
12
|
+
events: number;
|
|
13
|
+
errors: number;
|
|
14
|
+
failureCount: number;
|
|
15
|
+
lastEventAt: string | null;
|
|
16
|
+
}[];
|
|
17
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">>];
|