@damian87/omp 0.7.0 → 0.8.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.
Files changed (37) hide show
  1. package/README.md +29 -0
  2. package/dist/src/cli.js +310 -1
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/env/dotenv.d.ts +41 -0
  5. package/dist/src/env/dotenv.js +112 -0
  6. package/dist/src/env/dotenv.js.map +1 -0
  7. package/dist/src/env/init.d.ts +50 -0
  8. package/dist/src/env/init.js +276 -0
  9. package/dist/src/env/init.js.map +1 -0
  10. package/dist/src/gateway/connector.d.ts +37 -0
  11. package/dist/src/gateway/connector.js +12 -0
  12. package/dist/src/gateway/connector.js.map +1 -0
  13. package/dist/src/gateway/connectors/slack.d.ts +69 -0
  14. package/dist/src/gateway/connectors/slack.js +159 -0
  15. package/dist/src/gateway/connectors/slack.js.map +1 -0
  16. package/dist/src/gateway/registry.d.ts +29 -0
  17. package/dist/src/gateway/registry.js +37 -0
  18. package/dist/src/gateway/registry.js.map +1 -0
  19. package/dist/src/gateway/runtime.d.ts +58 -0
  20. package/dist/src/gateway/runtime.js +105 -0
  21. package/dist/src/gateway/runtime.js.map +1 -0
  22. package/dist/src/jira.js +2 -14
  23. package/dist/src/jira.js.map +1 -1
  24. package/dist/src/slack/config.d.ts +32 -0
  25. package/dist/src/slack/config.js +52 -0
  26. package/dist/src/slack/config.js.map +1 -0
  27. package/dist/src/slack/handler.d.ts +48 -0
  28. package/dist/src/slack/handler.js +68 -0
  29. package/dist/src/slack/handler.js.map +1 -0
  30. package/dist/src/slack/serve.d.ts +8 -0
  31. package/dist/src/slack/serve.js +7 -0
  32. package/dist/src/slack/serve.js.map +1 -0
  33. package/dist/src/team/tmux.d.ts +1 -0
  34. package/dist/src/team/tmux.js +9 -0
  35. package/dist/src/team/tmux.js.map +1 -1
  36. package/docs/slack-setup.md +144 -0
  37. package/package.json +3 -2
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Interactive setup for `~/.omp/.env`.
3
+ *
4
+ * `omp env init` walks the user through getting their Slack tokens, prompts
5
+ * them, writes the file (chmod 600), and tells them what to run next. Anything
6
+ * that would prompt the user lives behind injectable I/O so unit tests can
7
+ * exercise the full happy path and the validation/abort paths without ever
8
+ * touching a real terminal.
9
+ */
10
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { dirname, join } from "node:path";
13
+ import { OMP_ENV_DIRNAME, OMP_ENV_FILENAME } from "./dotenv.js";
14
+ const BOT_TOKEN_PREFIX = "xoxb-";
15
+ const APP_TOKEN_PREFIX = "xapp-";
16
+ const SLACK_APP_URL = "https://api.slack.com/apps";
17
+ /**
18
+ * Slack app manifest pre-configured for the omp gateway bridge. Includes the
19
+ * bot scopes Slack requires before letting you install the app, plus event
20
+ * subscriptions for DMs and @mentions, plus Socket Mode (no public URL).
21
+ *
22
+ * Keep this in sync with docs/slack-setup.md. If you change one, change both.
23
+ */
24
+ export const SLACK_APP_MANIFEST_YAML = `display_information:
25
+ name: omp-copilot
26
+ description: Bridge to a local GitHub Copilot CLI session
27
+ features:
28
+ bot_user:
29
+ display_name: omp-copilot
30
+ always_online: true
31
+ app_home:
32
+ messages_tab_enabled: true
33
+ messages_tab_read_only_enabled: false
34
+ oauth_config:
35
+ scopes:
36
+ bot:
37
+ - app_mentions:read
38
+ - chat:write
39
+ - im:history
40
+ - im:read
41
+ - im:write
42
+ settings:
43
+ event_subscriptions:
44
+ bot_events:
45
+ - app_mention
46
+ - message.im
47
+ interactivity:
48
+ is_enabled: false
49
+ org_deploy_enabled: false
50
+ socket_mode_enabled: true
51
+ `;
52
+ const INTRO_LINES = [
53
+ "",
54
+ "omp env init — set up ~/.omp/.env",
55
+ "",
56
+ "This writes your Slack tokens (and optional defaults) to ~/.omp/.env so",
57
+ "`omp gateway serve` works from any shell, without `source .env`.",
58
+ "Shell exports always win, so a one-off override still works.",
59
+ "",
60
+ "──────────────────────────────────────────────────────────────────────",
61
+ "STEP 1 — create the Slack app FROM AN APP MANIFEST (not from scratch).",
62
+ "──────────────────────────────────────────────────────────────────────",
63
+ ` • Open ${SLACK_APP_URL} → "Create New App" → "From an app manifest".`,
64
+ " • Pick your workspace.",
65
+ ` • Choose YAML and paste the manifest below, then "Create" → "Install to Workspace".`,
66
+ " (The manifest includes the required scopes so Slack will let you install it.",
67
+ " Picking 'From scratch' leaves scopes empty — Slack then refuses to install.)",
68
+ "",
69
+ "── manifest (copy from here to the next dashed line) ──",
70
+ ...SLACK_APP_MANIFEST_YAML.trimEnd().split("\n"),
71
+ "── end of manifest ──",
72
+ "",
73
+ "──────────────────────────────────────────────────────────────────────",
74
+ "STEP 2 — grab the two tokens (≈1 min):",
75
+ "──────────────────────────────────────────────────────────────────────",
76
+ ` • Bot token (xoxb-…): "OAuth & Permissions" → "Bot User OAuth Token"`,
77
+ " (visible after the 'Install to Workspace' step above).",
78
+ ` • App-level token (xapp-…): "Basic Information" → "App-Level Tokens"`,
79
+ " → Generate, with scope `connections:write`.",
80
+ "",
81
+ "Then paste both tokens at the prompts below. Press ENTER on optional ones to skip.",
82
+ "",
83
+ ];
84
+ /**
85
+ * Run the interactive setup. Idempotent — re-running shows masked existing
86
+ * values and offers to overwrite (or pass `force: true`).
87
+ *
88
+ * Validation:
89
+ * - bot token must start with `xoxb-` (we re-prompt up to 2 times)
90
+ * - app token must start with `xapp-` (we re-prompt up to 2 times)
91
+ * - empty bot/app token in non-interactive mode is an error
92
+ */
93
+ export async function runEnvInit(opts) {
94
+ const { io, force, answers } = opts;
95
+ const home = opts.homeDir ?? homedir();
96
+ const path = join(home, OMP_ENV_DIRNAME, OMP_ENV_FILENAME);
97
+ // Non-interactive path: take everything from `answers`. Used by the CLI
98
+ // when the user passes --bot-token / --app-token flags, or by tests.
99
+ const interactive = !answers;
100
+ if (interactive) {
101
+ for (const line of INTRO_LINES)
102
+ io.print(line);
103
+ }
104
+ if (existsSync(path) && !force) {
105
+ if (interactive) {
106
+ const existing = readExistingMasked(path);
107
+ io.print(`Existing config at ${path}:`);
108
+ for (const line of existing)
109
+ io.print(` ${line}`);
110
+ io.print("");
111
+ const overwrite = await io.ask("Overwrite? [y/N] ");
112
+ if ((overwrite ?? "").trim().toLowerCase() !== "y") {
113
+ return { ok: false, path, reason: "aborted by user (no overwrite)" };
114
+ }
115
+ }
116
+ else {
117
+ return { ok: false, path, reason: `${path} already exists (use --force to overwrite)` };
118
+ }
119
+ }
120
+ const collected = {
121
+ slackBotToken: "",
122
+ slackAppToken: "",
123
+ copilotTmuxSession: "",
124
+ slackAllowedUsers: "",
125
+ };
126
+ if (answers) {
127
+ Object.assign(collected, answers);
128
+ }
129
+ else {
130
+ collected.slackBotToken = await promptForToken(io, "Slack BOT token", BOT_TOKEN_PREFIX);
131
+ collected.slackAppToken = await promptForToken(io, "Slack APP-LEVEL token", APP_TOKEN_PREFIX);
132
+ collected.copilotTmuxSession = (await io.ask("Pin Copilot tmux session (optional, e.g. omp-9999): ")) ?? "";
133
+ collected.slackAllowedUsers = (await io.ask("Slack user ID allowlist (optional, comma-separated, e.g. U0123ABCD): ")) ?? "";
134
+ }
135
+ // Final validation — in both interactive and non-interactive modes the
136
+ // required tokens must be present and prefix-shaped, otherwise refuse to
137
+ // write a config that wouldn't pass `gateway doctor`.
138
+ const botToken = collected.slackBotToken.trim();
139
+ const appToken = collected.slackAppToken.trim();
140
+ if (!botToken || !botToken.startsWith(BOT_TOKEN_PREFIX)) {
141
+ return {
142
+ ok: false,
143
+ path,
144
+ reason: `Slack BOT token is required and must start with "${BOT_TOKEN_PREFIX}".`,
145
+ };
146
+ }
147
+ if (!appToken || !appToken.startsWith(APP_TOKEN_PREFIX)) {
148
+ return {
149
+ ok: false,
150
+ path,
151
+ reason: `Slack APP-LEVEL token is required and must start with "${APP_TOKEN_PREFIX}".`,
152
+ };
153
+ }
154
+ const session = collected.copilotTmuxSession.trim();
155
+ const users = collected.slackAllowedUsers.trim();
156
+ const content = renderEnvFile({
157
+ botToken,
158
+ appToken,
159
+ session: session || undefined,
160
+ users: users || undefined,
161
+ });
162
+ mkdirSync(dirname(path), { recursive: true });
163
+ // Atomic, perm-safe write. mkdtempSync creates a brand-new sibling dir with
164
+ // a unique suffix (mode 0o700 on POSIX), so the temp file inside is always
165
+ // freshly created — `{ mode: 0o600 }` is guaranteed to apply, and there's
166
+ // no chance of stomping a pre-existing temp file with stale perms. The
167
+ // final rename then atomically installs the 0o600 file into place.
168
+ let tmpDir = null;
169
+ let tmpFile = null;
170
+ try {
171
+ tmpDir = mkdtempSync(join(dirname(path), ".env-init-"));
172
+ tmpFile = join(tmpDir, "env");
173
+ writeFileSync(tmpFile, content, { mode: 0o600, encoding: "utf8" });
174
+ // Defense-in-depth: confirm the perms we asked for are what we got
175
+ // before we publish the file. On Windows the check is a no-op.
176
+ if (process.platform !== "win32") {
177
+ const mode = statSync(tmpFile).mode & 0o777;
178
+ if (mode !== 0o600) {
179
+ throw new Error(`temp file mode is ${mode.toString(8)}, expected 600`);
180
+ }
181
+ }
182
+ renameSync(tmpFile, path);
183
+ }
184
+ catch (err) {
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ return { ok: false, path, reason: `failed to write ${path}: ${msg}` };
187
+ }
188
+ finally {
189
+ if (tmpDir) {
190
+ try {
191
+ rmSync(tmpDir, { recursive: true, force: true });
192
+ }
193
+ catch {
194
+ /* best effort */
195
+ }
196
+ }
197
+ }
198
+ // POSIX: file was created with mode 0o600 and that survives the rename.
199
+ // Windows: mode bits are largely meaningless. Either way, we never end up
200
+ // in a state where we'd need to apologize for the perms — if anything went
201
+ // wrong above we'd have returned the error already.
202
+ const lockedDown = process.platform !== "win32";
203
+ if (interactive) {
204
+ io.print("");
205
+ io.print(lockedDown ? `Wrote ${path} (chmod 600).` : `Wrote ${path}.`);
206
+ io.print("");
207
+ io.print("Next:");
208
+ io.print(" 1. start a Copilot tmux session if one isn't running already");
209
+ io.print(" (any `omp-<digits>` name; e.g. `tmux new-session -d -s omp-9999`)");
210
+ io.print(" 2. `omp gateway status` — should report ready=true");
211
+ io.print(" 3. `omp gateway serve` — blocks; ^C to stop");
212
+ io.print("");
213
+ }
214
+ return { ok: true, path };
215
+ }
216
+ function renderEnvFile(k) {
217
+ const lines = [
218
+ "# Written by `omp env init`. Edit by hand or re-run the command.",
219
+ "# Precedence: shell exports always win over values in this file.",
220
+ "",
221
+ `SLACK_BOT_TOKEN=${k.botToken}`,
222
+ `SLACK_APP_TOKEN=${k.appToken}`,
223
+ ];
224
+ if (k.session)
225
+ lines.push(`COPILOT_TMUX_SESSION=${k.session}`);
226
+ if (k.users)
227
+ lines.push(`SLACK_ALLOWED_USERS=${k.users}`);
228
+ lines.push("");
229
+ return lines.join("\n");
230
+ }
231
+ async function promptForToken(io, label, prefix) {
232
+ for (let attempt = 0; attempt < 3; attempt++) {
233
+ const value = ((await io.ask(`${label} (starts with ${prefix}): `)) ?? "").trim();
234
+ if (value.startsWith(prefix))
235
+ return value;
236
+ if (!value) {
237
+ io.print(` ${label} is required.`);
238
+ continue;
239
+ }
240
+ io.print(` That doesn't look like a ${label.toLowerCase()} (expected to start with "${prefix}").`);
241
+ }
242
+ // Caller's final validation will reject — we don't throw here so the call
243
+ // site can return a structured error.
244
+ return "";
245
+ }
246
+ /** Read an existing file and return user-visible lines with values masked. */
247
+ function readExistingMasked(path) {
248
+ try {
249
+ return readFileSync(path, "utf8")
250
+ .split(/\r?\n/)
251
+ .filter((l) => l.trim() && !l.trim().startsWith("#"))
252
+ .map((line) => {
253
+ const i = line.indexOf("=");
254
+ if (i === -1)
255
+ return line;
256
+ const key = line.slice(0, i);
257
+ const val = line.slice(i + 1);
258
+ return `${key}=${maskValue(val)}`;
259
+ });
260
+ }
261
+ catch {
262
+ return ["(could not read existing file)"];
263
+ }
264
+ }
265
+ function maskValue(v) {
266
+ const trimmed = v.trim();
267
+ if (trimmed.length <= 4)
268
+ return "****";
269
+ // Show prefix (e.g. xoxb-) + 3 stars + last 4 chars to confirm identity
270
+ // without leaking the bulk of the secret.
271
+ const dashIndex = trimmed.indexOf("-");
272
+ const prefix = dashIndex >= 0 ? trimmed.slice(0, dashIndex + 1) : "";
273
+ const tail = trimmed.slice(-4);
274
+ return `${prefix}***${tail}`;
275
+ }
276
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/env/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A Gateway Connector is a long-lived event source that the gateway runtime
3
+ * starts, watches for readiness, and stops cleanly on shutdown. Connectors are
4
+ * intentionally tiny: name + lifecycle + status snapshot. All transport-specific
5
+ * concerns (Bolt, HTTP, MCP, etc.) live inside the connector implementation.
6
+ *
7
+ * Why not just call `runSlackBot()`? Adding a second connector later (Telegram,
8
+ * Discord, webhook) becomes one file implementing this interface, not a new
9
+ * top-level command and a new lifecycle.
10
+ */
11
+ export interface ConnectorStatus {
12
+ /** True when the connector is connected and ready to handle events. */
13
+ ready: boolean;
14
+ /** Optional human-readable detail — error message, "not started", etc. */
15
+ detail?: string;
16
+ }
17
+ export interface Connector {
18
+ /** Stable identifier, e.g. "slack". Used for `--only` filtering and status output. */
19
+ readonly name: string;
20
+ /** Open whatever long-lived resources are needed (sockets, listeners). */
21
+ start(): Promise<void>;
22
+ /** Close cleanly. Must be idempotent — calling stop() twice is a no-op. */
23
+ stop(): Promise<void>;
24
+ /** Synchronous readiness snapshot — never opens sockets or makes I/O calls. */
25
+ status(): ConnectorStatus;
26
+ }
27
+ /**
28
+ * Static (no-I/O) readiness check for the doctor command. Connectors expose
29
+ * this so `omp gateway status` can answer "is this startable?" without
30
+ * actually starting it.
31
+ */
32
+ export interface ConnectorDoctor {
33
+ /** Same name as the Connector. */
34
+ readonly name: string;
35
+ /** Returns a snapshot reporting whether start() would currently succeed. */
36
+ doctor(): ConnectorStatus;
37
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A Gateway Connector is a long-lived event source that the gateway runtime
3
+ * starts, watches for readiness, and stops cleanly on shutdown. Connectors are
4
+ * intentionally tiny: name + lifecycle + status snapshot. All transport-specific
5
+ * concerns (Bolt, HTTP, MCP, etc.) live inside the connector implementation.
6
+ *
7
+ * Why not just call `runSlackBot()`? Adding a second connector later (Telegram,
8
+ * Discord, webhook) becomes one file implementing this interface, not a new
9
+ * top-level command and a new lifecycle.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=connector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.js","sourceRoot":"","sources":["../../../src/gateway/connector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Slack {@link Connector} — wraps the @slack/bolt Socket Mode adapter that
3
+ * used to live in src/slack/serve.ts. Pure handler logic (handler.ts) and
4
+ * config loader (config.ts) are reused unchanged.
5
+ *
6
+ * Lifecycle:
7
+ * start() → instantiate Bolt App, auth.test for botUserId, subscribe to
8
+ * app.message (DMs) + app.event("app_mention"), then app.start().
9
+ * If anything throws, state.error is recorded and status reports
10
+ * not ready — the gateway runtime treats it as a failed start.
11
+ * stop() → idempotent app.stop().
12
+ * status()→ derived from internal state; never opens sockets.
13
+ */
14
+ import type { Connector, ConnectorDoctor } from "../connector.js";
15
+ import type { SlackConfig } from "../../slack/config.js";
16
+ import { type SlackHandlerDeps } from "../../slack/handler.js";
17
+ export interface BoltLike {
18
+ client: {
19
+ auth: {
20
+ test: () => Promise<{
21
+ user_id?: string;
22
+ }>;
23
+ };
24
+ };
25
+ start: () => Promise<unknown>;
26
+ stop: () => Promise<unknown>;
27
+ message: (handler: (args: {
28
+ message: SlackMessage;
29
+ say: SaySig;
30
+ }) => Promise<void>) => void;
31
+ event: (name: "app_mention", handler: (args: {
32
+ event: SlackMessage;
33
+ say: SaySig;
34
+ }) => Promise<void>) => void;
35
+ }
36
+ export type AppFactory = (config: SlackConfig) => BoltLike;
37
+ export type SaySig = (msg: {
38
+ text: string;
39
+ thread_ts?: string;
40
+ }) => Promise<unknown>;
41
+ export interface SlackMessage {
42
+ text?: string;
43
+ user?: string;
44
+ bot_id?: string;
45
+ subtype?: string;
46
+ channel_type?: string;
47
+ thread_ts?: string;
48
+ ts?: string;
49
+ }
50
+ export interface SlackConnectorOptions {
51
+ config: SlackConfig;
52
+ /** Inject an App factory for testing; default lazy-loads @slack/bolt. */
53
+ appFactory?: AppFactory;
54
+ /** Inject handler deps for testing; default wires comms resolveSession + commsAsk. */
55
+ handlerDeps?: Omit<SlackHandlerDeps, "allowedUsers" | "requireMention" | "sessionEnv">;
56
+ /** Logger; defaults to console.error. */
57
+ log?: (msg: string) => void;
58
+ }
59
+ export declare const SLACK_CONNECTOR_NAME = "slack";
60
+ /**
61
+ * Build a Slack {@link Connector}. The factory is sync; all I/O happens in
62
+ * `start()`.
63
+ */
64
+ export declare function createSlackConnector(opts: SlackConnectorOptions): Connector;
65
+ /**
66
+ * Static readiness check (no sockets opened). True when both tokens are
67
+ * present in the loaded config AND a Copilot tmux session can be resolved.
68
+ */
69
+ export declare function slackDoctor(config: SlackConfig | null, errorIfNoConfig?: string): ConnectorDoctor;
@@ -0,0 +1,159 @@
1
+ import { handleSlackMessage, } from "../../slack/handler.js";
2
+ import { resolveSession } from "../../comms/resolve-session.js";
3
+ import { commsAsk } from "../../comms/index.js";
4
+ export const SLACK_CONNECTOR_NAME = "slack";
5
+ async function defaultAppFactory(config) {
6
+ const bolt = await import("@slack/bolt");
7
+ // Bolt is CJS; default-import under NodeNext.
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ const Mod = bolt;
10
+ const App = Mod.App ?? Mod.default?.App;
11
+ if (!App)
12
+ throw new Error("@slack/bolt: App export not found");
13
+ return new App({
14
+ token: config.botToken,
15
+ appToken: config.appToken,
16
+ socketMode: true,
17
+ });
18
+ }
19
+ /**
20
+ * Build a Slack {@link Connector}. The factory is sync; all I/O happens in
21
+ * `start()`.
22
+ */
23
+ export function createSlackConnector(opts) {
24
+ const { config, appFactory, handlerDeps, log = (m) => console.error(m) } = opts;
25
+ let app;
26
+ let started = false;
27
+ let stopping = false;
28
+ let botUserId;
29
+ let lastError;
30
+ const baseDeps = {
31
+ resolve: handlerDeps?.resolve ?? ((o) => resolveSession(o)),
32
+ ask: handlerDeps?.ask ?? ((session, text) => commsAsk(session, text)),
33
+ allowedUsers: config.allowedUsers,
34
+ requireMention: config.requireMention,
35
+ sessionEnv: config.sessionEnv,
36
+ };
37
+ async function respond(input, say) {
38
+ try {
39
+ const res = await handleSlackMessage(input, baseDeps);
40
+ if (res.reply)
41
+ await say({ text: res.reply, thread_ts: res.threadTs });
42
+ }
43
+ catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ log(`omp slack: handler error: ${msg}`);
46
+ try {
47
+ await say({ text: ":warning: internal error handling your message.", thread_ts: input.threadTs });
48
+ }
49
+ catch {
50
+ /* best effort */
51
+ }
52
+ }
53
+ }
54
+ return {
55
+ name: SLACK_CONNECTOR_NAME,
56
+ async start() {
57
+ if (started)
58
+ return;
59
+ try {
60
+ app = appFactory ? appFactory(config) : await defaultAppFactory(config);
61
+ const auth = await app.client.auth.test();
62
+ botUserId = auth.user_id;
63
+ app.message(async ({ message, say }) => {
64
+ if (message.subtype || message.bot_id || message.user === botUserId)
65
+ return;
66
+ if (message.channel_type !== "im")
67
+ return; // DMs only
68
+ await respond({
69
+ text: message.text ?? "",
70
+ userId: message.user,
71
+ channelType: "im",
72
+ isMention: false,
73
+ // Only stay in-thread when the user wrote IN a thread. For top-level
74
+ // DMs we post inline — Slack hides DM thread replies under a
75
+ // "View thread" link, which makes the reply look like it never arrived.
76
+ threadTs: message.thread_ts,
77
+ botUserId,
78
+ }, say);
79
+ });
80
+ app.event("app_mention", async ({ event, say }) => {
81
+ if (event.bot_id || event.user === botUserId)
82
+ return;
83
+ await respond({
84
+ text: event.text ?? "",
85
+ userId: event.user,
86
+ channelType: "channel",
87
+ isMention: true,
88
+ threadTs: event.thread_ts ?? event.ts,
89
+ botUserId,
90
+ }, say);
91
+ });
92
+ await app.start();
93
+ started = true;
94
+ lastError = undefined;
95
+ log(`omp slack: connected via Socket Mode as ${botUserId ?? "bot"} — listening for DMs and @mentions.`);
96
+ }
97
+ catch (err) {
98
+ lastError = err instanceof Error ? err.message : String(err);
99
+ // Clean up partial state so a retry can proceed.
100
+ if (app) {
101
+ try {
102
+ await app.stop();
103
+ }
104
+ catch {
105
+ /* ignore */
106
+ }
107
+ }
108
+ app = undefined;
109
+ started = false;
110
+ throw err;
111
+ }
112
+ },
113
+ async stop() {
114
+ if (stopping)
115
+ return;
116
+ stopping = true;
117
+ try {
118
+ if (app && started) {
119
+ try {
120
+ await app.stop();
121
+ }
122
+ catch (err) {
123
+ log(`omp slack: stop error (ignored): ${err instanceof Error ? err.message : String(err)}`);
124
+ }
125
+ }
126
+ }
127
+ finally {
128
+ app = undefined;
129
+ started = false;
130
+ stopping = false;
131
+ }
132
+ },
133
+ status() {
134
+ if (started)
135
+ return { ready: true };
136
+ if (lastError)
137
+ return { ready: false, detail: lastError };
138
+ return { ready: false, detail: "not started" };
139
+ },
140
+ };
141
+ }
142
+ /**
143
+ * Static readiness check (no sockets opened). True when both tokens are
144
+ * present in the loaded config AND a Copilot tmux session can be resolved.
145
+ */
146
+ export function slackDoctor(config, errorIfNoConfig) {
147
+ return {
148
+ name: SLACK_CONNECTOR_NAME,
149
+ doctor() {
150
+ if (!config)
151
+ return { ready: false, detail: errorIfNoConfig ?? "missing slack tokens" };
152
+ const resolved = resolveSession({ env: config.sessionEnv });
153
+ if (!resolved.ok)
154
+ return { ready: false, detail: resolved.error };
155
+ return { ready: true, detail: `session=${resolved.session}` };
156
+ },
157
+ };
158
+ }
159
+ //# sourceMappingURL=slack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../../src/gateway/connectors/slack.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,kBAAkB,GAGnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAuChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C,KAAK,UAAU,iBAAiB,CAAC,MAAmB;IAClD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACzC,8CAA8C;IAC9C,8DAA8D;IAC9D,MAAM,GAAG,GAAG,IAAW,CAAC;IACxB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;IACxC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC/D,OAAO,IAAI,GAAG,CAAC;QACb,KAAK,EAAE,MAAM,CAAC,QAAQ;QACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,IAAI;KACjB,CAAa,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAA2B;IAC9D,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC;IAEhF,IAAI,GAAyB,CAAC;IAC9B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,SAA6B,CAAC;IAClC,IAAI,SAA6B,CAAC;IAElC,MAAM,QAAQ,GAAqB;QACjC,OAAO,EAAE,WAAW,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QAC3D,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrE,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC;IAEF,KAAK,UAAU,OAAO,CAAC,KAAwB,EAAE,GAAW;QAC1D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACtD,IAAI,GAAG,CAAC,KAAK;gBAAE,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,GAAG,CAAC,6BAA6B,GAAG,EAAE,CAAC,CAAC;YACxC,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,iDAAiD,EAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YACpG,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,oBAAoB;QAE1B,KAAK,CAAC,KAAK;YACT,IAAI,OAAO;gBAAE,OAAO;YACpB,IAAI,CAAC;gBACH,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBACxE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC1C,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC;gBAEzB,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;oBACrC,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;wBAAE,OAAO;oBAC5E,IAAI,OAAO,CAAC,YAAY,KAAK,IAAI;wBAAE,OAAO,CAAC,WAAW;oBACtD,MAAM,OAAO,CACX;wBACE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;wBACxB,MAAM,EAAE,OAAO,CAAC,IAAI;wBACpB,WAAW,EAAE,IAAI;wBACjB,SAAS,EAAE,KAAK;wBAChB,qEAAqE;wBACrE,6DAA6D;wBAC7D,wEAAwE;wBACxE,QAAQ,EAAE,OAAO,CAAC,SAAS;wBAC3B,SAAS;qBACV,EACD,GAAG,CACJ,CAAC;gBACJ,CAAC,CAAC,CAAC;gBAEH,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE;oBAChD,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;wBAAE,OAAO;oBACrD,MAAM,OAAO,CACX;wBACE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;wBACtB,MAAM,EAAE,KAAK,CAAC,IAAI;wBAClB,WAAW,EAAE,SAAS;wBACtB,SAAS,EAAE,IAAI;wBACf,QAAQ,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,EAAE;wBACrC,SAAS;qBACV,EACD,GAAG,CACJ,CAAC;gBACJ,CAAC,CAAC,CAAC;gBAEH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;gBAClB,OAAO,GAAG,IAAI,CAAC;gBACf,SAAS,GAAG,SAAS,CAAC;gBACtB,GAAG,CAAC,2CAA2C,SAAS,IAAI,KAAK,qCAAqC,CAAC,CAAC;YAC1G,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,iDAAiD;gBACjD,IAAI,GAAG,EAAE,CAAC;oBACR,IAAI,CAAC;wBACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;oBACnB,CAAC;oBAAC,MAAM,CAAC;wBACP,YAAY;oBACd,CAAC;gBACH,CAAC;gBACD,GAAG,GAAG,SAAS,CAAC;gBAChB,OAAO,GAAG,KAAK,CAAC;gBAChB,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAED,KAAK,CAAC,IAAI;YACR,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,OAAO,EAAE,CAAC;oBACnB,IAAI,CAAC;wBACH,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;oBACnB,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,GAAG,CAAC,oCAAoC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAC9F,CAAC;gBACH,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,GAAG,GAAG,SAAS,CAAC;gBAChB,OAAO,GAAG,KAAK,CAAC;gBAChB,QAAQ,GAAG,KAAK,CAAC;YACnB,CAAC;QACH,CAAC;QAED,MAAM;YACJ,IAAI,OAAO;gBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACpC,IAAI,SAAS;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAA0B,EAAE,eAAwB;IAC9E,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,MAAM;YACJ,IAAI,CAAC,MAAM;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,IAAI,sBAAsB,EAAE,CAAC;YACxF,MAAM,QAAQ,GAAG,cAAc,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;YAC5D,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;YAClE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAChE,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Connector registry — the seam where new connectors plug in. Today: slack
3
+ * only. To add Telegram/Discord/etc, register a factory here, drop a file
4
+ * under src/gateway/connectors/, and the CLI `--only <name>` picks it up.
5
+ */
6
+ import type { Connector, ConnectorDoctor } from "./connector.js";
7
+ export interface RegistryDeps {
8
+ /** Env overrides for tests; default reads process.env. */
9
+ env?: NodeJS.ProcessEnv;
10
+ }
11
+ export interface BuiltConnectors {
12
+ /** Live connectors to be started by the gateway runtime. */
13
+ connectors: Connector[];
14
+ /** Doctor probes — one per *potential* connector, started or not. */
15
+ doctors: ConnectorDoctor[];
16
+ /** Warnings encountered while building the registry (e.g. missing tokens). */
17
+ warnings: string[];
18
+ }
19
+ /**
20
+ * Build the set of connectors that should run given the current env. A
21
+ * connector that lacks its required env vars is reported as a doctor probe
22
+ * (so `gateway status` can show the gap) but is NOT added to `connectors`.
23
+ *
24
+ * `enabledNames` (from `--only`) further filters the set; passing undefined
25
+ * means "all auto-detected".
26
+ */
27
+ export declare function buildConnectors(enabledNames: string[] | undefined, deps?: RegistryDeps): BuiltConnectors;
28
+ /** Known connector names — used for `--only` validation messages. */
29
+ export declare const KNOWN_CONNECTORS: readonly string[];
@@ -0,0 +1,37 @@
1
+ import { createSlackConnector, slackDoctor, SLACK_CONNECTOR_NAME } from "./connectors/slack.js";
2
+ import { loadSlackConfig } from "../slack/config.js";
3
+ /**
4
+ * Build the set of connectors that should run given the current env. A
5
+ * connector that lacks its required env vars is reported as a doctor probe
6
+ * (so `gateway status` can show the gap) but is NOT added to `connectors`.
7
+ *
8
+ * `enabledNames` (from `--only`) further filters the set; passing undefined
9
+ * means "all auto-detected".
10
+ */
11
+ export function buildConnectors(enabledNames, deps = {}) {
12
+ const env = deps.env ?? process.env;
13
+ const warnings = [];
14
+ const connectors = [];
15
+ const doctors = [];
16
+ // --- slack ---
17
+ const slackEnabled = enabledNames ? enabledNames.includes(SLACK_CONNECTOR_NAME) : true;
18
+ if (slackEnabled) {
19
+ let slackCfg = null;
20
+ let slackErr;
21
+ try {
22
+ slackCfg = loadSlackConfig(undefined, env);
23
+ }
24
+ catch (err) {
25
+ slackErr = err instanceof Error ? err.message : String(err);
26
+ warnings.push(`slack: ${slackErr}`);
27
+ }
28
+ if (slackCfg) {
29
+ connectors.push(createSlackConnector({ config: slackCfg }));
30
+ }
31
+ doctors.push(slackDoctor(slackCfg, slackErr));
32
+ }
33
+ return { connectors, doctors, warnings };
34
+ }
35
+ /** Known connector names — used for `--only` validation messages. */
36
+ export const KNOWN_CONNECTORS = [SLACK_CONNECTOR_NAME];
37
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/gateway/registry.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAgBrD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAkC,EAClC,OAAqB,EAAE;IAEvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,gBAAgB;IAChB,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvF,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,QAAQ,GAAG,IAAI,CAAC;QACpB,IAAI,QAA4B,CAAC;QACjC,IAAI,CAAC;YACH,QAAQ,GAAG,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC3C,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,gBAAgB,GAAsB,CAAC,oBAAoB,CAAC,CAAC"}