@spinabot/brigade 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +11 -0
- package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js +18 -0
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +2 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +2 -0
- package/dist/agents/channels/sdk.js.map +1 -1
- package/dist/agents/channels/slack/account-config.d.ts +172 -0
- package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-config.js +353 -0
- package/dist/agents/channels/slack/account-config.js.map +1 -0
- package/dist/agents/channels/slack/account-registry.d.ts +45 -0
- package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-registry.js +58 -0
- package/dist/agents/channels/slack/account-registry.js.map +1 -0
- package/dist/agents/channels/slack/adapter.d.ts +66 -0
- package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
- package/dist/agents/channels/slack/adapter.js +547 -0
- package/dist/agents/channels/slack/adapter.js.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.js +71 -0
- package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
- package/dist/agents/channels/slack/approval-native.d.ts +70 -0
- package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-native.js +85 -0
- package/dist/agents/channels/slack/approval-native.js.map +1 -0
- package/dist/agents/channels/slack/blocks.d.ts +125 -0
- package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
- package/dist/agents/channels/slack/blocks.js +145 -0
- package/dist/agents/channels/slack/blocks.js.map +1 -0
- package/dist/agents/channels/slack/command-menu.d.ts +44 -0
- package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/slack/command-menu.js +66 -0
- package/dist/agents/channels/slack/command-menu.js.map +1 -0
- package/dist/agents/channels/slack/connection.d.ts +422 -0
- package/dist/agents/channels/slack/connection.d.ts.map +1 -0
- package/dist/agents/channels/slack/connection.js +1042 -0
- package/dist/agents/channels/slack/connection.js.map +1 -0
- package/dist/agents/channels/slack/directory-live.d.ts +129 -0
- package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
- package/dist/agents/channels/slack/directory-live.js +148 -0
- package/dist/agents/channels/slack/directory-live.js.map +1 -0
- package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
- package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
- package/dist/agents/channels/slack/draft-stream.js +218 -0
- package/dist/agents/channels/slack/draft-stream.js.map +1 -0
- package/dist/agents/channels/slack/format.d.ts +41 -0
- package/dist/agents/channels/slack/format.d.ts.map +1 -0
- package/dist/agents/channels/slack/format.js +271 -0
- package/dist/agents/channels/slack/format.js.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.js +257 -0
- package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
- package/dist/agents/channels/slack/index.d.ts +15 -0
- package/dist/agents/channels/slack/index.d.ts.map +1 -0
- package/dist/agents/channels/slack/index.js +15 -0
- package/dist/agents/channels/slack/index.js.map +1 -0
- package/dist/agents/channels/slack/media.d.ts +90 -0
- package/dist/agents/channels/slack/media.d.ts.map +1 -0
- package/dist/agents/channels/slack/media.js +215 -0
- package/dist/agents/channels/slack/media.js.map +1 -0
- package/dist/agents/channels/slack/module.d.ts +26 -0
- package/dist/agents/channels/slack/module.d.ts.map +1 -0
- package/dist/agents/channels/slack/module.js +67 -0
- package/dist/agents/channels/slack/module.js.map +1 -0
- package/dist/agents/channels/slack/plugin.d.ts +69 -0
- package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
- package/dist/agents/channels/slack/plugin.js +318 -0
- package/dist/agents/channels/slack/plugin.js.map +1 -0
- package/dist/agents/channels/slack/probe.d.ts +72 -0
- package/dist/agents/channels/slack/probe.d.ts.map +1 -0
- package/dist/agents/channels/slack/probe.js +103 -0
- package/dist/agents/channels/slack/probe.js.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.js +44 -0
- package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.js +68 -0
- package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
- package/dist/agents/channels/slack/user-directory.d.ts +69 -0
- package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
- package/dist/agents/channels/slack/user-directory.js +94 -0
- package/dist/agents/channels/slack/user-directory.js.map +1 -0
- package/dist/agents/channels/slack/webhook.d.ts +89 -0
- package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
- package/dist/agents/channels/slack/webhook.js +228 -0
- package/dist/agents/channels/slack/webhook.js.map +1 -0
- package/dist/agents/channels/telegram/adapter.d.ts.map +1 -1
- package/dist/agents/channels/telegram/adapter.js +10 -3
- package/dist/agents/channels/telegram/adapter.js.map +1 -1
- package/dist/agents/channels/telegram/connection.d.ts +10 -0
- package/dist/agents/channels/telegram/connection.d.ts.map +1 -1
- package/dist/agents/channels/telegram/connection.js +161 -5
- package/dist/agents/channels/telegram/connection.js.map +1 -1
- package/dist/agents/channels/telegram/format.d.ts +17 -0
- package/dist/agents/channels/telegram/format.d.ts.map +1 -1
- package/dist/agents/channels/telegram/format.js +53 -1
- package/dist/agents/channels/telegram/format.js.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts +17 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.js +68 -7
- package/dist/agents/channels/telegram/inbound-extras.js.map +1 -1
- package/dist/agents/channels/telegram/media.d.ts +8 -0
- package/dist/agents/channels/telegram/media.d.ts.map +1 -1
- package/dist/agents/channels/telegram/media.js +30 -2
- package/dist/agents/channels/telegram/media.js.map +1 -1
- package/dist/agents/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/agents/channels/telegram/webhook.js +7 -1
- package/dist/agents/channels/telegram/webhook.js.map +1 -1
- package/dist/agents/extensions/modules/index.d.ts.map +1 -1
- package/dist/agents/extensions/modules/index.js +5 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/agents/extensions/types.d.ts +11 -0
- package/dist/agents/extensions/types.d.ts.map +1 -1
- package/dist/agents/extensions/types.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/convex-cmd.d.ts +2 -1
- package/dist/cli/commands/convex-cmd.d.ts.map +1 -1
- package/dist/cli/commands/convex-cmd.js +79 -6
- package/dist/cli/commands/convex-cmd.js.map +1 -1
- package/dist/cli/program/build-program.js +1 -1
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +24 -5
- package/dist/core/server.js.map +1 -1
- package/package.json +4 -1
- package/scripts/convex-dev.mjs +28 -2
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack config-shape helpers (multi-WORKSPACE aware; mirrors Telegram's
|
|
3
|
+
* `account-config.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Slack needs MORE than one secret per workspace, unlike Telegram's single bot
|
|
6
|
+
* token:
|
|
7
|
+
* - `botToken` (`xoxb-…`) — the bot user token; every Web API call uses it.
|
|
8
|
+
* - `appToken` (`xapp-…`) — the app-level token; REQUIRED for Socket Mode
|
|
9
|
+
* (opens the events websocket via
|
|
10
|
+
* `apps.connections.open`).
|
|
11
|
+
* - `signingSecret` — HMAC secret; REQUIRED for Events-API (HTTP)
|
|
12
|
+
* mode to verify Slack's request signature.
|
|
13
|
+
* - `userToken` (`xoxp-…`) — OPTIONAL; user-scoped token for reads that the
|
|
14
|
+
* bot token can't do (e.g. some file fetches).
|
|
15
|
+
*
|
|
16
|
+
* Two config shapes are recognised so the surface lines up with WhatsApp /
|
|
17
|
+
* Telegram:
|
|
18
|
+
*
|
|
19
|
+
* Legacy (single-workspace):
|
|
20
|
+
* channels.slack = { enabled: true, botToken: "xoxb-…", appToken: "xapp-…" }
|
|
21
|
+
*
|
|
22
|
+
* Multi-workspace:
|
|
23
|
+
* channels.slack = {
|
|
24
|
+
* enabled: true,
|
|
25
|
+
* accounts: [
|
|
26
|
+
* { id: "acme", botToken: "xoxb-AAA", appToken: "xapp-AAA" },
|
|
27
|
+
* { id: "labs", botToken: "xoxb-BBB", appToken: "xapp-BBB" },
|
|
28
|
+
* ],
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* A legacy config with no `accounts[]` reads as `[{ id: "default" }]`.
|
|
32
|
+
*
|
|
33
|
+
* Token resolution mirrors Telegram: a `${VAR}` ref expands against
|
|
34
|
+
* `process.env`; otherwise the literal passes through; then — for the bot token
|
|
35
|
+
* only — a DURABLE sealed token (written by `connect_channel`) is consulted so
|
|
36
|
+
* the channel survives a reboot; finally a per-secret env var is the last-resort
|
|
37
|
+
* fallback.
|
|
38
|
+
*
|
|
39
|
+
* Honesty note on sealing: `connect_channel` seals exactly ONE secret per
|
|
40
|
+
* channel, under `channel:slack` — that is the BOT token. The app-level token
|
|
41
|
+
* and signing secret are NOT durably sealed today; they must come from config
|
|
42
|
+
* (`${VAR}`/literal) or their per-secret env var (`SLACK_APP_TOKEN` /
|
|
43
|
+
* `SLACK_SIGNING_SECRET`). If a future `connect_channel` learns to seal those
|
|
44
|
+
* under `channel:slack:app` / `channel:slack:signing`, wire the sealed-read
|
|
45
|
+
* fallback back into `resolveSlackAppToken` / `resolveSlackSigningSecret`.
|
|
46
|
+
*/
|
|
47
|
+
import { readSealedChannelToken } from "../channel-secrets.js";
|
|
48
|
+
const CHANNEL_ID = "slack";
|
|
49
|
+
const DEFAULT_ACCOUNT_ID = "default";
|
|
50
|
+
/** Per-secret env vars consulted as a last-resort fallback. */
|
|
51
|
+
const BOT_TOKEN_ENV_VAR = "SLACK_BOT_TOKEN";
|
|
52
|
+
const APP_TOKEN_ENV_VAR = "SLACK_APP_TOKEN";
|
|
53
|
+
const SIGNING_SECRET_ENV_VAR = "SLACK_SIGNING_SECRET";
|
|
54
|
+
const USER_TOKEN_ENV_VAR = "SLACK_USER_TOKEN";
|
|
55
|
+
/**
|
|
56
|
+
* Standard proxy env vars consulted as a last-resort proxy fallback, in
|
|
57
|
+
* precedence order. Lower-case wins over upper-case (curl/undici convention),
|
|
58
|
+
* and a TLS-oriented `https_proxy` outranks the catch-all `ALL_PROXY`. These
|
|
59
|
+
* mirror the keys undici's own `EnvHttpProxyAgent` honours (same set Telegram
|
|
60
|
+
* uses).
|
|
61
|
+
*/
|
|
62
|
+
const PROXY_ENV_VARS = ["https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"];
|
|
63
|
+
/**
|
|
64
|
+
* Sealed-token key for the bot token (`channel:slack`) — the ONLY Slack secret
|
|
65
|
+
* `connect_channel` seals today. The app token + signing secret are not sealed
|
|
66
|
+
* (see the header note), so their resolvers pass `sealKey: null`.
|
|
67
|
+
*/
|
|
68
|
+
const SEAL_KEY_BOT = CHANNEL_ID; // `channel:slack`
|
|
69
|
+
/** `${VAR}` secret-ref form — identical to `config/io.ts`'s SECRET_REF_PATTERN. */
|
|
70
|
+
const SECRET_REF_PATTERN = /^\$\{([A-Z_][A-Z0-9_]*)\}$/;
|
|
71
|
+
/** Default gateway path the Events-API transport registers + Slack POSTs to. */
|
|
72
|
+
const DEFAULT_EVENTS_PATH = "/slack/events";
|
|
73
|
+
/** Read `channels.slack` loosely (schema keeps it open). */
|
|
74
|
+
function slackChannelConfig(cfg) {
|
|
75
|
+
return cfg.channels?.[CHANNEL_ID];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a single token-ish string: a `${VAR}` ref expands against
|
|
79
|
+
* `process.env`; any other non-empty string passes through verbatim; empty /
|
|
80
|
+
* missing returns "".
|
|
81
|
+
*/
|
|
82
|
+
function resolveTokenRef(raw, env) {
|
|
83
|
+
if (typeof raw !== "string")
|
|
84
|
+
return "";
|
|
85
|
+
const trimmed = raw.trim();
|
|
86
|
+
if (!trimmed)
|
|
87
|
+
return "";
|
|
88
|
+
const m = SECRET_REF_PATTERN.exec(trimmed);
|
|
89
|
+
if (m && m[1])
|
|
90
|
+
return (env[m[1]] ?? "").trim();
|
|
91
|
+
return trimmed;
|
|
92
|
+
}
|
|
93
|
+
/** Is the Slack channel switched on at all (any shape)? */
|
|
94
|
+
export function slackChannelEnabled(cfg) {
|
|
95
|
+
return slackChannelConfig(cfg)?.enabled === true;
|
|
96
|
+
}
|
|
97
|
+
/** List configured account ids. Legacy single-account configs surface `["default"]`. */
|
|
98
|
+
export function listSlackAccountIds(cfg) {
|
|
99
|
+
const slot = slackChannelConfig(cfg);
|
|
100
|
+
if (!slot || slot.enabled !== true)
|
|
101
|
+
return [];
|
|
102
|
+
const accounts = Array.isArray(slot.accounts) ? slot.accounts : undefined;
|
|
103
|
+
if (!accounts || accounts.length === 0)
|
|
104
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
105
|
+
const ids = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const entry of accounts) {
|
|
108
|
+
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
|
109
|
+
if (!id || seen.has(id))
|
|
110
|
+
continue;
|
|
111
|
+
seen.add(id);
|
|
112
|
+
ids.push(id);
|
|
113
|
+
}
|
|
114
|
+
// A half-typed `accounts:[]` still degrades to the default account so the
|
|
115
|
+
// channel isn't silently disabled.
|
|
116
|
+
return ids.length === 0 ? [DEFAULT_ACCOUNT_ID] : ids;
|
|
117
|
+
}
|
|
118
|
+
/** Look up the raw account entry from config (or null when missing). */
|
|
119
|
+
function findAccountEntry(cfg, accountId) {
|
|
120
|
+
const slot = slackChannelConfig(cfg);
|
|
121
|
+
if (!slot)
|
|
122
|
+
return null;
|
|
123
|
+
const accounts = Array.isArray(slot.accounts) ? slot.accounts : undefined;
|
|
124
|
+
if (!accounts)
|
|
125
|
+
return null;
|
|
126
|
+
for (const entry of accounts) {
|
|
127
|
+
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
|
128
|
+
if (id === accountId)
|
|
129
|
+
return entry;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve one Slack secret for an account. Precedence (mirrors Telegram's token
|
|
135
|
+
* resolution): per-account config `${VAR}`/literal → top-level config → durable
|
|
136
|
+
* sealed token → per-secret env var. Returns `""` when nothing resolves.
|
|
137
|
+
*/
|
|
138
|
+
function resolveSecret(cfg, accountId, field, sealKey, envVar, env) {
|
|
139
|
+
const id = accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
140
|
+
const slot = slackChannelConfig(cfg);
|
|
141
|
+
const entry = findAccountEntry(cfg, id);
|
|
142
|
+
const perAccount = resolveTokenRef(entry?.[field], env);
|
|
143
|
+
if (perAccount)
|
|
144
|
+
return perAccount;
|
|
145
|
+
const topLevel = resolveTokenRef(slot?.[field], env);
|
|
146
|
+
if (topLevel)
|
|
147
|
+
return topLevel;
|
|
148
|
+
if (sealKey) {
|
|
149
|
+
const sealed = readSealedChannelToken(sealKey);
|
|
150
|
+
if (sealed)
|
|
151
|
+
return sealed;
|
|
152
|
+
}
|
|
153
|
+
if (envVar) {
|
|
154
|
+
const fromEnv = (env[envVar] ?? "").trim();
|
|
155
|
+
if (fromEnv)
|
|
156
|
+
return fromEnv;
|
|
157
|
+
}
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
/** Resolve the bot user token (`xoxb-…`) for an account. */
|
|
161
|
+
export function resolveSlackBotToken(cfg, accountId, env = process.env) {
|
|
162
|
+
return resolveSecret(cfg, accountId, "botToken", SEAL_KEY_BOT, BOT_TOKEN_ENV_VAR, env);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resolve the app-level token (`xapp-…`, Socket Mode) for an account. NOT sealed
|
|
166
|
+
* by `connect_channel` today (sealKey `null`) — comes from config or
|
|
167
|
+
* `SLACK_APP_TOKEN`.
|
|
168
|
+
*/
|
|
169
|
+
export function resolveSlackAppToken(cfg, accountId, env = process.env) {
|
|
170
|
+
return resolveSecret(cfg, accountId, "appToken", null, APP_TOKEN_ENV_VAR, env);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve the HMAC signing secret (Events-API mode) for an account. NOT sealed
|
|
174
|
+
* by `connect_channel` today (sealKey `null`) — comes from config or
|
|
175
|
+
* `SLACK_SIGNING_SECRET`.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveSlackSigningSecret(cfg, accountId, env = process.env) {
|
|
178
|
+
return resolveSecret(cfg, accountId, "signingSecret", null, SIGNING_SECRET_ENV_VAR, env);
|
|
179
|
+
}
|
|
180
|
+
/** Resolve the optional user token (`xoxp-…`) for an account. */
|
|
181
|
+
export function resolveSlackUserToken(cfg, accountId, env = process.env) {
|
|
182
|
+
return resolveSecret(cfg, accountId, "userToken", null, USER_TOKEN_ENV_VAR, env);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve the proxy URL all Slack API calls (+ the Socket Mode websocket) should
|
|
186
|
+
* route through. Precedence (mirrors Telegram's `resolveTelegramProxyUrl`):
|
|
187
|
+
* 1. The per-account `accounts[].proxy` (multi-workspace shape), `${VAR}`-resolved.
|
|
188
|
+
* 2. The top-level `channels.slack.proxy`, `${VAR}`-resolved.
|
|
189
|
+
* 3. The first set standard proxy env var (`https_proxy` / `HTTPS_PROXY` /
|
|
190
|
+
* `all_proxy` / `ALL_PROXY`) — last-resort fallback.
|
|
191
|
+
* Returns `""` when none is configured → a DIRECT connection (the default,
|
|
192
|
+
* byte-unchanged from before proxy support existed).
|
|
193
|
+
*
|
|
194
|
+
* `${VAR}` refs are resolved here the same way `botToken` is, so an operator can
|
|
195
|
+
* keep the proxy (which may carry `user:pass@` creds) out of the committed
|
|
196
|
+
* config as `channels.slack.proxy: "${SLACK_PROXY}"`.
|
|
197
|
+
*/
|
|
198
|
+
export function resolveSlackProxyUrl(cfg, accountId, env = process.env) {
|
|
199
|
+
const id = accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
200
|
+
const slot = slackChannelConfig(cfg);
|
|
201
|
+
const entry = findAccountEntry(cfg, id);
|
|
202
|
+
const perAccount = resolveTokenRef(entry?.proxy, env);
|
|
203
|
+
if (perAccount)
|
|
204
|
+
return perAccount;
|
|
205
|
+
const topLevel = resolveTokenRef(slot?.proxy, env);
|
|
206
|
+
if (topLevel)
|
|
207
|
+
return topLevel;
|
|
208
|
+
for (const key of PROXY_ENV_VARS) {
|
|
209
|
+
const value = (env[key] ?? "").trim();
|
|
210
|
+
if (value)
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Mask a proxy URL down to `scheme://host:port` (creds + path dropped) so it is
|
|
217
|
+
* safe to log. A malformed URL is reduced to its scheme only; an empty input
|
|
218
|
+
* returns "". NEVER log a raw proxy URL — it may embed `user:pass@`.
|
|
219
|
+
*/
|
|
220
|
+
export function maskProxyUrl(proxyUrl) {
|
|
221
|
+
const raw = (proxyUrl ?? "").trim();
|
|
222
|
+
if (!raw)
|
|
223
|
+
return "";
|
|
224
|
+
try {
|
|
225
|
+
const u = new URL(raw);
|
|
226
|
+
return `${u.protocol}//${u.host}`; // host includes :port; userinfo + path/query dropped
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
const scheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(raw)?.[1];
|
|
230
|
+
return scheme ? `${scheme}://<masked>` : "<masked>";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Resolve a per-account view of the config (defaults + token resolution filled in). */
|
|
234
|
+
export function resolveSlackAccount(cfg, accountId, env = process.env) {
|
|
235
|
+
const slot = slackChannelConfig(cfg);
|
|
236
|
+
const id = accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
237
|
+
const entry = findAccountEntry(cfg, id);
|
|
238
|
+
const enabled = entry?.enabled !== false && slot?.enabled === true;
|
|
239
|
+
return {
|
|
240
|
+
accountId: id,
|
|
241
|
+
enabled,
|
|
242
|
+
botToken: resolveSlackBotToken(cfg, id, env),
|
|
243
|
+
appToken: resolveSlackAppToken(cfg, id, env),
|
|
244
|
+
signingSecret: resolveSlackSigningSecret(cfg, id, env),
|
|
245
|
+
userToken: resolveSlackUserToken(cfg, id, env),
|
|
246
|
+
proxyUrl: resolveSlackProxyUrl(cfg, id, env),
|
|
247
|
+
verbose: slot?.verbose === true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve the Slack transport config. Defaults to `socket` (Brigade is
|
|
252
|
+
* local-first and Socket Mode needs no public URL); `events` is opt-in via
|
|
253
|
+
* `channels.slack.mode: "events"`.
|
|
254
|
+
*/
|
|
255
|
+
export function slackEventsConfig(cfg) {
|
|
256
|
+
const slot = slackChannelConfig(cfg);
|
|
257
|
+
const rawMode = typeof slot?.mode === "string" ? slot.mode.trim().toLowerCase() : "";
|
|
258
|
+
const mode = rawMode === "events" || rawMode === "http" ? "events" : "socket";
|
|
259
|
+
const ev = slot?.events ?? {};
|
|
260
|
+
const path = typeof ev.path === "string" && ev.path.trim() ? ev.path.trim() : DEFAULT_EVENTS_PATH;
|
|
261
|
+
return {
|
|
262
|
+
mode,
|
|
263
|
+
url: typeof ev.url === "string" ? ev.url.trim() : "",
|
|
264
|
+
path: path.startsWith("/") ? path : `/${path}`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Normalize a path to a leading-slash, trailing-slash-stripped form so derived
|
|
269
|
+
* per-account paths concatenate cleanly. `"/slack/events/"` → `"/slack/events"`.
|
|
270
|
+
*/
|
|
271
|
+
function normalizeEventsPath(raw) {
|
|
272
|
+
const trimmed = (raw ?? "").trim();
|
|
273
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
274
|
+
return withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Resolve the events-mode gateway route path for ONE account. Multi-workspace
|
|
278
|
+
* installs need a DISTINCT path per workspace (Slack POSTs to a per-app URL, and
|
|
279
|
+
* the gateway routes by exact path):
|
|
280
|
+
*
|
|
281
|
+
* - default account → the configured base events path (`channels.slack.events.
|
|
282
|
+
* path`, default `/slack/events`) — byte-identical to the single-workspace
|
|
283
|
+
* path so an existing default-only install is unchanged.
|
|
284
|
+
* - a named account → its explicit `accounts[].webhookPath` when set
|
|
285
|
+
* (`${VAR}`-resolved), else the derived `${basePath}/<accountId>` so two
|
|
286
|
+
* workspaces can't collide on one route.
|
|
287
|
+
*
|
|
288
|
+
* The base path is read from `slackEventsConfig`, so a custom
|
|
289
|
+
* `channels.slack.events.path` propagates to every derived per-account path.
|
|
290
|
+
*/
|
|
291
|
+
export function resolveSlackEventsPath(cfg, accountId, env = process.env) {
|
|
292
|
+
const id = accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
293
|
+
const basePath = normalizeEventsPath(slackEventsConfig(cfg).path);
|
|
294
|
+
if (id === DEFAULT_ACCOUNT_ID)
|
|
295
|
+
return basePath;
|
|
296
|
+
const entry = findAccountEntry(cfg, id);
|
|
297
|
+
const explicit = resolveTokenRef(entry?.webhookPath, env);
|
|
298
|
+
if (explicit)
|
|
299
|
+
return normalizeEventsPath(explicit);
|
|
300
|
+
// Derive a collision-free path from the account id (path-segment-safe slug).
|
|
301
|
+
const slug = id.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || id;
|
|
302
|
+
return `${basePath}/${slug}`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* True when live reply-streaming is enabled (`channels.slack.liveStream`).
|
|
306
|
+
* Default OFF — the adapter delivers one final chunked message.
|
|
307
|
+
*/
|
|
308
|
+
export function slackLiveStreamEnabled(cfg) {
|
|
309
|
+
return slackChannelConfig(cfg)?.liveStream === true;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Resolve the streaming edit throttle in ms. Reads
|
|
313
|
+
* `channels.slack.streamThrottleMs`; falls back to 1000ms (floored at 250ms by
|
|
314
|
+
* the draft-stream).
|
|
315
|
+
*/
|
|
316
|
+
export function slackStreamThrottleMs(cfg) {
|
|
317
|
+
const raw = slackChannelConfig(cfg)?.streamThrottleMs;
|
|
318
|
+
return typeof raw === "number" && raw > 0 ? raw : 1000;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* True when reasoning surfacing is enabled (`channels.slack.surfaceReasoning`).
|
|
322
|
+
* Default OFF — `<think>` reasoning is stripped from channel replies as today.
|
|
323
|
+
*/
|
|
324
|
+
export function slackSurfaceReasoning(cfg) {
|
|
325
|
+
return slackChannelConfig(cfg)?.surfaceReasoning === true;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Resolve the idle-thread-session TTL in ms, or `null` when unset / disabled.
|
|
329
|
+
* Accepts a number (ms) or a duration string (`"6h"`, `"30m"`, …). The cron
|
|
330
|
+
* session-reaper uses this to age out idle Slack thread sessions.
|
|
331
|
+
*/
|
|
332
|
+
export function slackThreadIdleTtlMs(cfg) {
|
|
333
|
+
const raw = slackChannelConfig(cfg)?.threadIdleTtlMs;
|
|
334
|
+
if (typeof raw === "number")
|
|
335
|
+
return raw > 0 ? raw : null;
|
|
336
|
+
if (typeof raw !== "string")
|
|
337
|
+
return null;
|
|
338
|
+
const trimmed = raw.trim();
|
|
339
|
+
if (!trimmed)
|
|
340
|
+
return null;
|
|
341
|
+
const m = /^(\d+)\s*(s|m|h|d|w)?$/i.exec(trimmed);
|
|
342
|
+
if (!m)
|
|
343
|
+
return null;
|
|
344
|
+
const n = Number(m[1]);
|
|
345
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
346
|
+
return null;
|
|
347
|
+
const unit = (m[2] ?? "ms").toLowerCase();
|
|
348
|
+
const mult = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000, ms: 1 };
|
|
349
|
+
const factor = mult[unit] ?? 1;
|
|
350
|
+
return n * factor;
|
|
351
|
+
}
|
|
352
|
+
export { CHANNEL_ID as SLACK_CHANNEL_ID, DEFAULT_ACCOUNT_ID as SLACK_DEFAULT_ACCOUNT_ID, BOT_TOKEN_ENV_VAR as SLACK_BOT_TOKEN_ENV_VAR, APP_TOKEN_ENV_VAR as SLACK_APP_TOKEN_ENV_VAR, SIGNING_SECRET_ENV_VAR as SLACK_SIGNING_SECRET_ENV_VAR, USER_TOKEN_ENV_VAR as SLACK_USER_TOKEN_ENV_VAR, };
|
|
353
|
+
//# sourceMappingURL=account-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-config.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/account-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-account Slack adapter registry — the cross-module bridge that lets the
|
|
3
|
+
* Events-API webhook routes (registered at module `register()` time in
|
|
4
|
+
* `module.ts`) find the per-account adapters that are STARTED later by
|
|
5
|
+
* `createSlackPlugin` (`plugin.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Why this exists. In multi-workspace events mode each account has its OWN
|
|
8
|
+
* inbound webhook path (`/slack/events/<accountId>`). The gateway only accepts
|
|
9
|
+
* `b.httpRoute(...)` registrations during a module's `register()`, but the
|
|
10
|
+
* per-account adapters that those routes must feed are owned by the plugin's
|
|
11
|
+
* `startAccount` lifecycle, which runs AFTER `register()`. So the module
|
|
12
|
+
* registers one route per configured account up front, each route resolving its
|
|
13
|
+
* account's started adapter through THIS registry at request time (a thunk),
|
|
14
|
+
* and the plugin populates the registry on `startAccount` / clears it on
|
|
15
|
+
* `stopAccount`. Same shape + rationale as the channel approval-dispatcher
|
|
16
|
+
* registry (`approval-router.ts`): a process-wide singleton avoids threading the
|
|
17
|
+
* map through the gateway boot, and a single Slack workspace path is unaffected
|
|
18
|
+
* (the legacy single-adapter module owns its own route + adapter directly).
|
|
19
|
+
*
|
|
20
|
+
* Pinned via `resolveGlobalSingleton` so a hot reload / dual-build run shares
|
|
21
|
+
* one map (identical to the approval router).
|
|
22
|
+
*/
|
|
23
|
+
/** The minimal adapter surface a webhook route needs to feed inbound events. */
|
|
24
|
+
export interface SlackAccountSink {
|
|
25
|
+
/** Feed a parsed Slack payload into the started adapter's inbound path. */
|
|
26
|
+
feedWebhookEvent(kind: "event" | "interactive" | "slash", payload: unknown): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Register a started per-account adapter so its events-mode webhook route can
|
|
30
|
+
* feed inbound into it. The plugin calls this on `startAccount`. Idempotent —
|
|
31
|
+
* re-registering replaces the previous entry (restart-friendly).
|
|
32
|
+
*/
|
|
33
|
+
export declare function registerSlackAccountSink(accountId: string, sink: SlackAccountSink): void;
|
|
34
|
+
/**
|
|
35
|
+
* Drop a per-account adapter (the plugin calls this on `stopAccount`) so a
|
|
36
|
+
* torn-down workspace's route can't feed a dead adapter.
|
|
37
|
+
*/
|
|
38
|
+
export declare function removeSlackAccountSink(accountId: string): void;
|
|
39
|
+
/** Look up a started per-account adapter (or undefined when not started). */
|
|
40
|
+
export declare function getSlackAccountSink(accountId: string): SlackAccountSink | undefined;
|
|
41
|
+
/** Diagnostic — the account ids with a live sink. */
|
|
42
|
+
export declare function listSlackAccountSinks(): string[];
|
|
43
|
+
/** Test-only — clear every registered sink. */
|
|
44
|
+
export declare function resetSlackAccountSinksForTests(): void;
|
|
45
|
+
//# sourceMappingURL=account-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-registry.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/account-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAChC,2EAA2E;IAC3E,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAClF;AAcD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAExF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEnF;AAED,qDAAqD;AACrD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,+CAA+C;AAC/C,wBAAgB,8BAA8B,IAAI,IAAI,CAErD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-account Slack adapter registry — the cross-module bridge that lets the
|
|
3
|
+
* Events-API webhook routes (registered at module `register()` time in
|
|
4
|
+
* `module.ts`) find the per-account adapters that are STARTED later by
|
|
5
|
+
* `createSlackPlugin` (`plugin.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Why this exists. In multi-workspace events mode each account has its OWN
|
|
8
|
+
* inbound webhook path (`/slack/events/<accountId>`). The gateway only accepts
|
|
9
|
+
* `b.httpRoute(...)` registrations during a module's `register()`, but the
|
|
10
|
+
* per-account adapters that those routes must feed are owned by the plugin's
|
|
11
|
+
* `startAccount` lifecycle, which runs AFTER `register()`. So the module
|
|
12
|
+
* registers one route per configured account up front, each route resolving its
|
|
13
|
+
* account's started adapter through THIS registry at request time (a thunk),
|
|
14
|
+
* and the plugin populates the registry on `startAccount` / clears it on
|
|
15
|
+
* `stopAccount`. Same shape + rationale as the channel approval-dispatcher
|
|
16
|
+
* registry (`approval-router.ts`): a process-wide singleton avoids threading the
|
|
17
|
+
* map through the gateway boot, and a single Slack workspace path is unaffected
|
|
18
|
+
* (the legacy single-adapter module owns its own route + adapter directly).
|
|
19
|
+
*
|
|
20
|
+
* Pinned via `resolveGlobalSingleton` so a hot reload / dual-build run shares
|
|
21
|
+
* one map (identical to the approval router).
|
|
22
|
+
*/
|
|
23
|
+
import { resolveGlobalSingleton } from "../../../shared/global-singleton.js";
|
|
24
|
+
import { SLACK_DEFAULT_ACCOUNT_ID } from "./account-config.js";
|
|
25
|
+
const SLACK_ACCOUNT_SINKS_KEY = Symbol.for("brigade.slack.accountSinks");
|
|
26
|
+
/** Keyed by accountId — one started adapter per workspace. */
|
|
27
|
+
const sinks = resolveGlobalSingleton(SLACK_ACCOUNT_SINKS_KEY, () => new Map());
|
|
28
|
+
function normalizeAccountId(accountId) {
|
|
29
|
+
return accountId && accountId.trim() ? accountId.trim() : SLACK_DEFAULT_ACCOUNT_ID;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Register a started per-account adapter so its events-mode webhook route can
|
|
33
|
+
* feed inbound into it. The plugin calls this on `startAccount`. Idempotent —
|
|
34
|
+
* re-registering replaces the previous entry (restart-friendly).
|
|
35
|
+
*/
|
|
36
|
+
export function registerSlackAccountSink(accountId, sink) {
|
|
37
|
+
sinks.set(normalizeAccountId(accountId), sink);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Drop a per-account adapter (the plugin calls this on `stopAccount`) so a
|
|
41
|
+
* torn-down workspace's route can't feed a dead adapter.
|
|
42
|
+
*/
|
|
43
|
+
export function removeSlackAccountSink(accountId) {
|
|
44
|
+
sinks.delete(normalizeAccountId(accountId));
|
|
45
|
+
}
|
|
46
|
+
/** Look up a started per-account adapter (or undefined when not started). */
|
|
47
|
+
export function getSlackAccountSink(accountId) {
|
|
48
|
+
return sinks.get(normalizeAccountId(accountId));
|
|
49
|
+
}
|
|
50
|
+
/** Diagnostic — the account ids with a live sink. */
|
|
51
|
+
export function listSlackAccountSinks() {
|
|
52
|
+
return [...sinks.keys()];
|
|
53
|
+
}
|
|
54
|
+
/** Test-only — clear every registered sink. */
|
|
55
|
+
export function resetSlackAccountSinksForTests() {
|
|
56
|
+
sinks.clear();
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=account-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-registry.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/account-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAQ/D,MAAM,uBAAuB,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAEzE,8DAA8D;AAC9D,MAAM,KAAK,GAAG,sBAAsB,CACnC,uBAAuB,EACvB,GAAG,EAAE,CAAC,IAAI,GAAG,EAA4B,CACzC,CAAC;AAEF,SAAS,kBAAkB,CAAC,SAAoC;IAC/D,OAAO,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,SAAiB,EAAE,IAAsB;IACjF,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACvD,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACpD,OAAO,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,qBAAqB;IACpC,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,8BAA8B;IAC7C,KAAK,CAAC,KAAK,EAAE,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Brigade `ChannelAdapter` contract on top of the Socket Mode +
|
|
5
|
+
* Web API connection. Like Telegram, Slack is TOKEN-based: the operator pastes a
|
|
6
|
+
* bot token (`xoxb-…`) + an app-level token (`xapp-…`, for Socket Mode) from the
|
|
7
|
+
* Slack app config, so this adapter declares a `setup` wizard (two credentials)
|
|
8
|
+
* and has NO QR/link flow. Enablement is explicit — `channels.slack.enabled:
|
|
9
|
+
* true` plus a resolvable bot token.
|
|
10
|
+
*
|
|
11
|
+
* Modeled directly on `telegram/adapter.ts`: same health-flag mirroring, same
|
|
12
|
+
* deferred-media passthrough on inbound, same chunk-then-send outbound shape
|
|
13
|
+
* (chunk markdown ≤8000, convert each chunk to Slack mrkdwn, send with
|
|
14
|
+
* `mrkdwn: true`). Slack mrkdwn never "fails to parse" the way Telegram HTML can,
|
|
15
|
+
* so the outbound path is simpler — an empty rendered chunk falls back to the
|
|
16
|
+
* raw chunk, but there's no parse-error retry.
|
|
17
|
+
*
|
|
18
|
+
* Capabilities: edit (chat.update), unsend (chat.delete), reactions
|
|
19
|
+
* (reactions.add/remove), reply (thread_ts), threads, media (files.uploadV2),
|
|
20
|
+
* and Block Kit buttons. NO polls / forum-topic-create / programmatic command
|
|
21
|
+
* menu (Slack slash commands are registered in the app config UI).
|
|
22
|
+
*/
|
|
23
|
+
import { type ChannelAdapter, type ChannelCapabilities } from "../sdk.js";
|
|
24
|
+
import { type ConnectSlackArgs, type SlackConnection } from "./connection.js";
|
|
25
|
+
/** Adapter construction options — all optional for back-compat. */
|
|
26
|
+
export interface CreateSlackAdapterOptions {
|
|
27
|
+
/** Per-account (workspace) scope. Defaults to `"default"` (single-account). */
|
|
28
|
+
accountId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* TEST SEAM: override how the connection is built. Production leaves this
|
|
31
|
+
* undefined and `connectSlack` lazy-loads the Slack SDKs. Tests inject a fake.
|
|
32
|
+
*/
|
|
33
|
+
connectImpl?: (args: ConnectSlackArgs) => Promise<SlackConnection>;
|
|
34
|
+
}
|
|
35
|
+
export declare function createSlackAdapter(opts?: CreateSlackAdapterOptions): ChannelAdapter;
|
|
36
|
+
/**
|
|
37
|
+
* Synthesise the agent-facing note for an inbound reaction. The reaction itself
|
|
38
|
+
* carries no text, so the note ("<who> reacted :emoji: to message <id>") is what
|
|
39
|
+
* the central pipeline routes through dispatchTurn so the agent has context.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildReactionNote(emojis: string[], targetMessageId: string, fromName?: string): string;
|
|
42
|
+
/** Static Slack capability flags (shared by the legacy adapter + plugin meta). */
|
|
43
|
+
export declare const SLACK_CAPABILITIES: ChannelCapabilities;
|
|
44
|
+
/**
|
|
45
|
+
* The Slack adapter shape with its webhook + transport extensions. `createSlack
|
|
46
|
+
* Adapter` returns a `ChannelAdapter`, but the concrete object ALSO carries
|
|
47
|
+
* `feedWebhookEvent` + `transportMode` (not in the base contract). Callers that
|
|
48
|
+
* need them cast through this type.
|
|
49
|
+
*/
|
|
50
|
+
export interface SlackAdapter extends ChannelAdapter {
|
|
51
|
+
/**
|
|
52
|
+
* Feed a raw Slack payload (events-API mode). The gateway HTTP route calls
|
|
53
|
+
* this after verifying the signing-secret signature. No-op when not started or
|
|
54
|
+
* in socket mode. `kind` selects the event family.
|
|
55
|
+
*/
|
|
56
|
+
feedWebhookEvent(kind: "event" | "interactive" | "slash", payload: unknown): void;
|
|
57
|
+
/** The transport mode this adapter's connection runs (`"socket"` | `"events"`). */
|
|
58
|
+
transportMode(): "socket" | "events" | "unstarted";
|
|
59
|
+
/**
|
|
60
|
+
* Epoch ms of the most recent inbound event (liveness diagnostic), or null
|
|
61
|
+
* before the first event / when not started. Observability only — never flips
|
|
62
|
+
* health to "down" (a quiet channel is legitimately idle).
|
|
63
|
+
*/
|
|
64
|
+
lastEventAt(): number | null;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAQH,OAAO,EACN,KAAK,cAAc,EAEnB,KAAK,mBAAmB,EASxB,MAAM,WAAW,CAAC;AAiBnB,OAAO,EAAgB,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAQ5F,mEAAmE;AACnE,MAAM,WAAW,yBAAyB;IACzC,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,yBAA8B,GAAG,cAAc,CAoevF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAItG;AAED,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,EAAE,mBAQhC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IACnD;;;;OAIG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAClF,mFAAmF;IACnF,aAAa,IAAI,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IACnD;;;;OAIG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;CAC7B"}
|