@damian87/omp 0.7.0 → 0.9.2
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/.github/skills/slack/SKILL.md +82 -0
- package/README.md +61 -34
- package/catalog/capabilities.json +46 -0
- package/catalog/skills-general.json +26 -0
- package/dist/src/cli.js +365 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/copilot/version.js +10 -0
- package/dist/src/copilot/version.js.map +1 -1
- package/dist/src/env/dotenv.d.ts +41 -0
- package/dist/src/env/dotenv.js +112 -0
- package/dist/src/env/dotenv.js.map +1 -0
- package/dist/src/env/init.d.ts +56 -0
- package/dist/src/env/init.js +294 -0
- package/dist/src/env/init.js.map +1 -0
- package/dist/src/gateway/connector.d.ts +37 -0
- package/dist/src/gateway/connector.js +12 -0
- package/dist/src/gateway/connector.js.map +1 -0
- package/dist/src/gateway/connectors/slack.d.ts +69 -0
- package/dist/src/gateway/connectors/slack.js +159 -0
- package/dist/src/gateway/connectors/slack.js.map +1 -0
- package/dist/src/gateway/notify.d.ts +35 -0
- package/dist/src/gateway/notify.js +261 -0
- package/dist/src/gateway/notify.js.map +1 -0
- package/dist/src/gateway/registry.d.ts +29 -0
- package/dist/src/gateway/registry.js +37 -0
- package/dist/src/gateway/registry.js.map +1 -0
- package/dist/src/gateway/runtime.d.ts +58 -0
- package/dist/src/gateway/runtime.js +105 -0
- package/dist/src/gateway/runtime.js.map +1 -0
- package/dist/src/gateway/target-parser.d.ts +76 -0
- package/dist/src/gateway/target-parser.js +105 -0
- package/dist/src/gateway/target-parser.js.map +1 -0
- package/dist/src/jira.js +2 -14
- package/dist/src/jira.js.map +1 -1
- package/dist/src/schedule/commands.js +1 -0
- package/dist/src/schedule/commands.js.map +1 -1
- package/dist/src/schedule/runner.d.ts +9 -0
- package/dist/src/schedule/runner.js +31 -1
- package/dist/src/schedule/runner.js.map +1 -1
- package/dist/src/schedule/types.d.ts +9 -0
- package/dist/src/slack/config.d.ts +32 -0
- package/dist/src/slack/config.js +52 -0
- package/dist/src/slack/config.js.map +1 -0
- package/dist/src/slack/handler.d.ts +48 -0
- package/dist/src/slack/handler.js +68 -0
- package/dist/src/slack/handler.js.map +1 -0
- package/dist/src/slack/serve.d.ts +8 -0
- package/dist/src/slack/serve.js +7 -0
- package/dist/src/slack/serve.js.map +1 -0
- package/dist/src/team/tmux.d.ts +1 -0
- package/dist/src/team/tmux.js +9 -0
- package/dist/src/team/tmux.js.map +1 -1
- package/docs/slack-setup.md +177 -0
- package/package.json +13 -4
- package/plugin.json +12 -4
- package/scripts/lib/version-check.mjs +3 -0
- package/dist/src/mcp/server.d.ts +0 -10
- package/dist/src/mcp/server.js +0 -44
- package/dist/src/mcp/server.js.map +0 -1
- package/dist/src/mcp/tools/daily-log.d.ts +0 -2
- package/dist/src/mcp/tools/daily-log.js +0 -148
- package/dist/src/mcp/tools/daily-log.js.map +0 -1
- package/dist/src/mcp/tools/index.d.ts +0 -9
- package/dist/src/mcp/tools/index.js +0 -15
- package/dist/src/mcp/tools/index.js.map +0 -1
- package/dist/src/mcp/tools/notepad.d.ts +0 -2
- package/dist/src/mcp/tools/notepad.js +0 -135
- package/dist/src/mcp/tools/notepad.js.map +0 -1
- package/dist/src/mcp/tools/project-memory.d.ts +0 -2
- package/dist/src/mcp/tools/project-memory.js +0 -91
- package/dist/src/mcp/tools/project-memory.js.map +0 -1
- package/dist/src/mcp/tools/shared-memory.d.ts +0 -2
- package/dist/src/mcp/tools/shared-memory.js +0 -148
- package/dist/src/mcp/tools/shared-memory.js.map +0 -1
- package/dist/src/mcp/tools/state.d.ts +0 -2
- package/dist/src/mcp/tools/state.js +0 -107
- package/dist/src/mcp/tools/state.js.map +0 -1
- package/dist/src/mcp/tools/trace.d.ts +0 -10
- package/dist/src/mcp/tools/trace.js +0 -102
- package/dist/src/mcp/tools/trace.js.map +0 -1
- package/dist/src/mcp/types.d.ts +0 -29
- package/dist/src/mcp/types.js +0 -7
- package/dist/src/mcp/types.js.map +0 -1
- package/dist/test/catalog.test.d.ts +0 -1
- package/dist/test/catalog.test.js +0 -21
- package/dist/test/catalog.test.js.map +0 -1
- package/dist/test/jira.test.d.ts +0 -1
- package/dist/test/jira.test.js +0 -26
- package/dist/test/jira.test.js.map +0 -1
- package/dist/test/lint.test.d.ts +0 -1
- package/dist/test/lint.test.js +0 -9
- package/dist/test/lint.test.js.map +0 -1
- package/dist/test/sync.test.d.ts +0 -1
- package/dist/test/sync.test.js +0 -15
- package/dist/test/sync.test.js.map +0 -1
|
@@ -0,0 +1,294 @@
|
|
|
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
|
+
slackHomeChannel: "",
|
|
126
|
+
};
|
|
127
|
+
if (answers) {
|
|
128
|
+
Object.assign(collected, answers);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
collected.slackBotToken = await promptForToken(io, "Slack BOT token", BOT_TOKEN_PREFIX);
|
|
132
|
+
collected.slackAppToken = await promptForToken(io, "Slack APP-LEVEL token", APP_TOKEN_PREFIX);
|
|
133
|
+
collected.copilotTmuxSession = (await io.ask("Pin Copilot tmux session (optional, e.g. omp-9999): ")) ?? "";
|
|
134
|
+
collected.slackAllowedUsers = (await io.ask("Slack user ID allowlist (optional, comma-separated, e.g. U0123ABCD): ")) ?? "";
|
|
135
|
+
collected.slackHomeChannel = (await io.ask("Default Slack target for notifications (optional, channel C…/G…/D… or user U…): ")) ?? "";
|
|
136
|
+
}
|
|
137
|
+
// Final validation — in both interactive and non-interactive modes the
|
|
138
|
+
// required tokens must be present and prefix-shaped, otherwise refuse to
|
|
139
|
+
// write a config that wouldn't pass `gateway doctor`.
|
|
140
|
+
const botToken = collected.slackBotToken.trim();
|
|
141
|
+
const appToken = collected.slackAppToken.trim();
|
|
142
|
+
if (!botToken || !botToken.startsWith(BOT_TOKEN_PREFIX)) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
path,
|
|
146
|
+
reason: `Slack BOT token is required and must start with "${BOT_TOKEN_PREFIX}".`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (!appToken || !appToken.startsWith(APP_TOKEN_PREFIX)) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
path,
|
|
153
|
+
reason: `Slack APP-LEVEL token is required and must start with "${APP_TOKEN_PREFIX}".`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const session = collected.copilotTmuxSession.trim();
|
|
157
|
+
const users = collected.slackAllowedUsers.trim();
|
|
158
|
+
const homeChannel = collected.slackHomeChannel.trim();
|
|
159
|
+
// Light validation: looksLikeSlackId catches typos before they cause a
|
|
160
|
+
// bewildering BAD_HOME_CHANNEL error at notify-time.
|
|
161
|
+
if (homeChannel) {
|
|
162
|
+
const { looksLikeSlackId } = await import("../gateway/target-parser.js");
|
|
163
|
+
if (!looksLikeSlackId(homeChannel)) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
path,
|
|
167
|
+
reason: `SLACK_HOME_CHANNEL "${homeChannel}" doesn't look like a Slack ID (C…/G…/D…/U… plus 8+ uppercase alphanumeric chars).`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const content = renderEnvFile({
|
|
172
|
+
botToken,
|
|
173
|
+
appToken,
|
|
174
|
+
session: session || undefined,
|
|
175
|
+
users: users || undefined,
|
|
176
|
+
homeChannel: homeChannel || undefined,
|
|
177
|
+
});
|
|
178
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
179
|
+
// Atomic, perm-safe write. mkdtempSync creates a brand-new sibling dir with
|
|
180
|
+
// a unique suffix (mode 0o700 on POSIX), so the temp file inside is always
|
|
181
|
+
// freshly created — `{ mode: 0o600 }` is guaranteed to apply, and there's
|
|
182
|
+
// no chance of stomping a pre-existing temp file with stale perms. The
|
|
183
|
+
// final rename then atomically installs the 0o600 file into place.
|
|
184
|
+
let tmpDir = null;
|
|
185
|
+
let tmpFile = null;
|
|
186
|
+
try {
|
|
187
|
+
tmpDir = mkdtempSync(join(dirname(path), ".env-init-"));
|
|
188
|
+
tmpFile = join(tmpDir, "env");
|
|
189
|
+
writeFileSync(tmpFile, content, { mode: 0o600, encoding: "utf8" });
|
|
190
|
+
// Defense-in-depth: confirm the perms we asked for are what we got
|
|
191
|
+
// before we publish the file. On Windows the check is a no-op.
|
|
192
|
+
if (process.platform !== "win32") {
|
|
193
|
+
const mode = statSync(tmpFile).mode & 0o777;
|
|
194
|
+
if (mode !== 0o600) {
|
|
195
|
+
throw new Error(`temp file mode is ${mode.toString(8)}, expected 600`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
renameSync(tmpFile, path);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
202
|
+
return { ok: false, path, reason: `failed to write ${path}: ${msg}` };
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
if (tmpDir) {
|
|
206
|
+
try {
|
|
207
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
/* best effort */
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// POSIX: file was created with mode 0o600 and that survives the rename.
|
|
215
|
+
// Windows: mode bits are largely meaningless. Either way, we never end up
|
|
216
|
+
// in a state where we'd need to apologize for the perms — if anything went
|
|
217
|
+
// wrong above we'd have returned the error already.
|
|
218
|
+
const lockedDown = process.platform !== "win32";
|
|
219
|
+
if (interactive) {
|
|
220
|
+
io.print("");
|
|
221
|
+
io.print(lockedDown ? `Wrote ${path} (chmod 600).` : `Wrote ${path}.`);
|
|
222
|
+
io.print("");
|
|
223
|
+
io.print("Next:");
|
|
224
|
+
io.print(" 1. start a Copilot tmux session if one isn't running already");
|
|
225
|
+
io.print(" (any `omp-<digits>` name; e.g. `tmux new-session -d -s omp-9999`)");
|
|
226
|
+
io.print(" 2. `omp gateway status` — should report ready=true");
|
|
227
|
+
io.print(" 3. `omp gateway serve` — blocks; ^C to stop");
|
|
228
|
+
io.print("");
|
|
229
|
+
}
|
|
230
|
+
return { ok: true, path };
|
|
231
|
+
}
|
|
232
|
+
function renderEnvFile(k) {
|
|
233
|
+
const lines = [
|
|
234
|
+
"# Written by `omp env init`. Edit by hand or re-run the command.",
|
|
235
|
+
"# Precedence: shell exports always win over values in this file.",
|
|
236
|
+
"",
|
|
237
|
+
`SLACK_BOT_TOKEN=${k.botToken}`,
|
|
238
|
+
`SLACK_APP_TOKEN=${k.appToken}`,
|
|
239
|
+
];
|
|
240
|
+
if (k.session)
|
|
241
|
+
lines.push(`COPILOT_TMUX_SESSION=${k.session}`);
|
|
242
|
+
if (k.users)
|
|
243
|
+
lines.push(`SLACK_ALLOWED_USERS=${k.users}`);
|
|
244
|
+
if (k.homeChannel)
|
|
245
|
+
lines.push(`SLACK_HOME_CHANNEL=${k.homeChannel}`);
|
|
246
|
+
lines.push("");
|
|
247
|
+
return lines.join("\n");
|
|
248
|
+
}
|
|
249
|
+
async function promptForToken(io, label, prefix) {
|
|
250
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
251
|
+
const value = ((await io.ask(`${label} (starts with ${prefix}): `)) ?? "").trim();
|
|
252
|
+
if (value.startsWith(prefix))
|
|
253
|
+
return value;
|
|
254
|
+
if (!value) {
|
|
255
|
+
io.print(` ${label} is required.`);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
io.print(` That doesn't look like a ${label.toLowerCase()} (expected to start with "${prefix}").`);
|
|
259
|
+
}
|
|
260
|
+
// Caller's final validation will reject — we don't throw here so the call
|
|
261
|
+
// site can return a structured error.
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
/** Read an existing file and return user-visible lines with values masked. */
|
|
265
|
+
function readExistingMasked(path) {
|
|
266
|
+
try {
|
|
267
|
+
return readFileSync(path, "utf8")
|
|
268
|
+
.split(/\r?\n/)
|
|
269
|
+
.filter((l) => l.trim() && !l.trim().startsWith("#"))
|
|
270
|
+
.map((line) => {
|
|
271
|
+
const i = line.indexOf("=");
|
|
272
|
+
if (i === -1)
|
|
273
|
+
return line;
|
|
274
|
+
const key = line.slice(0, i);
|
|
275
|
+
const val = line.slice(i + 1);
|
|
276
|
+
return `${key}=${maskValue(val)}`;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return ["(could not read existing file)"];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function maskValue(v) {
|
|
284
|
+
const trimmed = v.trim();
|
|
285
|
+
if (trimmed.length <= 4)
|
|
286
|
+
return "****";
|
|
287
|
+
// Show prefix (e.g. xoxb-) + 3 stars + last 4 chars to confirm identity
|
|
288
|
+
// without leaking the bulk of the secret.
|
|
289
|
+
const dashIndex = trimmed.indexOf("-");
|
|
290
|
+
const prefix = dashIndex >= 0 ? trimmed.slice(0, dashIndex + 1) : "";
|
|
291
|
+
const tail = trimmed.slice(-4);
|
|
292
|
+
return `${prefix}***${tail}`;
|
|
293
|
+
}
|
|
294
|
+
//# 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":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface NotifyDeps {
|
|
2
|
+
/** Override env (tests). Defaults to process.env. */
|
|
3
|
+
env?: NodeJS.ProcessEnv;
|
|
4
|
+
/** Override fetch (tests). Defaults to global fetch. */
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
/** Override sleep (tests bypass backoff). */
|
|
7
|
+
sleep?: (ms: number) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface NotifyOptions {
|
|
10
|
+
text: string;
|
|
11
|
+
/** Explicit target. When omitted, falls back to `SLACK_HOME_CHANNEL`. */
|
|
12
|
+
target?: string;
|
|
13
|
+
/** Optional thread to reply in (overrides target's :thread_ts suffix). */
|
|
14
|
+
threadTs?: string;
|
|
15
|
+
/** Max total wait incl. retries. Default 10s. */
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export type NotifyErrorCode = "MISSING_TOKEN" | "MISSING_TARGET" | "BAD_TARGET" | "BAD_HOME_CHANNEL" | "OPEN_FAILED" | "POST_FAILED" | "RATE_LIMITED" | "TIMEOUT" | "NETWORK_ERROR";
|
|
19
|
+
export type NotifyResult = {
|
|
20
|
+
ok: true;
|
|
21
|
+
channel: string;
|
|
22
|
+
ts: string;
|
|
23
|
+
/** True when we had to call conversations.open to map U… → D…. */
|
|
24
|
+
openedIm: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
code: NotifyErrorCode;
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Send a Slack message. Library entry point — never throws. Returns a
|
|
32
|
+
* structured result so callers (cron / `/slack send` skill / CLI) decide
|
|
33
|
+
* how to surface or persist failures.
|
|
34
|
+
*/
|
|
35
|
+
export declare function notify(opts: NotifyOptions, deps?: NotifyDeps): Promise<NotifyResult>;
|