@cuylabs/channel-slack 0.1.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/LICENSE +201 -0
- package/README.md +168 -0
- package/dist/activity-ByrD9Ftr.d.ts +66 -0
- package/dist/assistant.d.ts +58 -0
- package/dist/assistant.js +188 -0
- package/dist/bolt.d.ts +344 -0
- package/dist/bolt.js +705 -0
- package/dist/chunk-BODPT4I6.js +322 -0
- package/dist/chunk-FPCE5V5Y.js +292 -0
- package/dist/chunk-FX2JOVX5.js +405 -0
- package/dist/chunk-JZG4IETE.js +141 -0
- package/dist/chunk-NE57BLLU.js +0 -0
- package/dist/chunk-TWJGVDA2.js +108 -0
- package/dist/core.d.ts +425 -0
- package/dist/core.js +42 -0
- package/dist/diagnostics.d.ts +105 -0
- package/dist/diagnostics.js +8 -0
- package/dist/feedback.d.ts +137 -0
- package/dist/feedback.js +128 -0
- package/dist/history.d.ts +266 -0
- package/dist/history.js +747 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +57 -0
- package/dist/logging-Bl3HfcC8.d.ts +8 -0
- package/dist/policy.d.ts +130 -0
- package/dist/policy.js +16 -0
- package/dist/setup.d.ts +165 -0
- package/dist/setup.js +453 -0
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +43 -0
- package/dist/targets.d.ts +113 -0
- package/dist/targets.js +484 -0
- package/dist/users.d.ts +109 -0
- package/dist/users.js +240 -0
- package/docs/concepts/activity.md +33 -0
- package/docs/concepts/bolt-runtime.md +30 -0
- package/docs/concepts/message-policy.md +49 -0
- package/docs/concepts/setup-requirements.md +44 -0
- package/docs/concepts/supplemental-history.md +55 -0
- package/docs/recipes/app-mention-handler.md +34 -0
- package/docs/recipes/assistant-thread-handler.md +28 -0
- package/docs/recipes/generate-slack-manifest.md +28 -0
- package/docs/recipes/history-visibility.md +36 -0
- package/docs/recipes/socket-mode-app.md +29 -0
- package/docs/reference/channel-slack-boundary.md +50 -0
- package/docs/reference/exports.md +32 -0
- package/package.json +130 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// src/diagnostics/inspect.ts
|
|
2
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 2500;
|
|
3
|
+
var DEFAULT_SCOPE_METHODS = [
|
|
4
|
+
"auth.test",
|
|
5
|
+
"auth.scopes",
|
|
6
|
+
"apps.permissions.info"
|
|
7
|
+
];
|
|
8
|
+
async function inspectSlackConnection(options = {}) {
|
|
9
|
+
const startedAt = Date.now();
|
|
10
|
+
const resolved = await resolveDiagnosticsClient(options);
|
|
11
|
+
const requiredScopes = normalizeScopes(options.requiredScopes ?? []);
|
|
12
|
+
const optionalScopes = normalizeScopes(options.optionalScopes ?? []);
|
|
13
|
+
const base = {
|
|
14
|
+
tokenSource: resolved.tokenSource,
|
|
15
|
+
requiredScopes,
|
|
16
|
+
optionalScopes,
|
|
17
|
+
missingRequiredScopes: [],
|
|
18
|
+
missingOptionalScopes: [],
|
|
19
|
+
findings: []
|
|
20
|
+
};
|
|
21
|
+
if (!resolved.client) {
|
|
22
|
+
return {
|
|
23
|
+
...base,
|
|
24
|
+
ok: false,
|
|
25
|
+
elapsedMs: elapsed(startedAt),
|
|
26
|
+
missingRequiredScopes: requiredScopes,
|
|
27
|
+
findings: [
|
|
28
|
+
{
|
|
29
|
+
severity: "error",
|
|
30
|
+
code: "missing-token",
|
|
31
|
+
message: "Slack token is missing. Pass `token`, inject `client`, or set SLACK_BOT_TOKEN."
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
error: { message: "Slack token is missing.", code: "missing-token" }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
38
|
+
const tokenArgs = resolved.token ? { token: resolved.token } : void 0;
|
|
39
|
+
let authPayload;
|
|
40
|
+
try {
|
|
41
|
+
authPayload = await withRequestTimeout(
|
|
42
|
+
() => resolved.client.auth.test(tokenArgs),
|
|
43
|
+
requestTimeoutMs,
|
|
44
|
+
"auth.test"
|
|
45
|
+
);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const apiError = toSlackApiInspectionError(error, "auth.test");
|
|
48
|
+
return {
|
|
49
|
+
...base,
|
|
50
|
+
ok: false,
|
|
51
|
+
elapsedMs: elapsed(startedAt),
|
|
52
|
+
missingRequiredScopes: requiredScopes,
|
|
53
|
+
findings: [
|
|
54
|
+
{
|
|
55
|
+
severity: "error",
|
|
56
|
+
code: "auth-test-failed",
|
|
57
|
+
message: apiError.message
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
error: apiError
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const authError = slackPayloadError(authPayload, "auth.test");
|
|
64
|
+
if (authError) {
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
ok: false,
|
|
68
|
+
elapsedMs: elapsed(startedAt),
|
|
69
|
+
missingRequiredScopes: requiredScopes,
|
|
70
|
+
auth: normalizeAuthInfo(authPayload),
|
|
71
|
+
findings: [
|
|
72
|
+
{
|
|
73
|
+
severity: "error",
|
|
74
|
+
code: "auth-test-failed",
|
|
75
|
+
message: authError.message
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
error: authError
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const auth = normalizeAuthInfo(authPayload);
|
|
82
|
+
const findings = [];
|
|
83
|
+
let scopes;
|
|
84
|
+
let missingRequiredScopes = [];
|
|
85
|
+
let missingOptionalScopes = [];
|
|
86
|
+
if (options.inspectScopes !== false) {
|
|
87
|
+
scopes = await inspectSlackTokenScopes({
|
|
88
|
+
token: resolved.token,
|
|
89
|
+
client: resolved.client,
|
|
90
|
+
requestTimeoutMs,
|
|
91
|
+
methods: options.scopeMethods
|
|
92
|
+
});
|
|
93
|
+
if (scopes.ok) {
|
|
94
|
+
missingRequiredScopes = missingScopes(requiredScopes, scopes.scopes);
|
|
95
|
+
missingOptionalScopes = missingScopes(optionalScopes, scopes.scopes);
|
|
96
|
+
} else if (requiredScopes.length > 0) {
|
|
97
|
+
missingRequiredScopes = requiredScopes;
|
|
98
|
+
}
|
|
99
|
+
if (!scopes.ok) {
|
|
100
|
+
findings.push({
|
|
101
|
+
severity: requiredScopes.length > 0 ? "error" : "warning",
|
|
102
|
+
code: "scope-inspection-unavailable",
|
|
103
|
+
message: requiredScopes.length > 0 ? "Slack token scopes could not be inspected, so required scopes could not be verified." : "Slack token scopes could not be inspected."
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (missingRequiredScopes.length > 0) {
|
|
108
|
+
findings.push({
|
|
109
|
+
severity: "error",
|
|
110
|
+
code: "missing-required-scopes",
|
|
111
|
+
message: `Slack token is missing required scope(s): ${missingRequiredScopes.join(
|
|
112
|
+
", "
|
|
113
|
+
)}.`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (missingOptionalScopes.length > 0) {
|
|
117
|
+
findings.push({
|
|
118
|
+
severity: "warning",
|
|
119
|
+
code: "missing-optional-scopes",
|
|
120
|
+
message: `Slack token is missing optional scope(s): ${missingOptionalScopes.join(
|
|
121
|
+
", "
|
|
122
|
+
)}.`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...base,
|
|
127
|
+
ok: findings.every((finding) => finding.severity !== "error"),
|
|
128
|
+
elapsedMs: elapsed(startedAt),
|
|
129
|
+
auth,
|
|
130
|
+
...scopes ? { scopes } : {},
|
|
131
|
+
missingRequiredScopes,
|
|
132
|
+
missingOptionalScopes,
|
|
133
|
+
findings
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function inspectSlackTokenScopes(options = {}) {
|
|
137
|
+
const resolved = await resolveDiagnosticsClient(options);
|
|
138
|
+
if (!resolved.client) {
|
|
139
|
+
return {
|
|
140
|
+
ok: false,
|
|
141
|
+
scopes: [],
|
|
142
|
+
errors: [
|
|
143
|
+
{
|
|
144
|
+
method: "auth.test",
|
|
145
|
+
error: {
|
|
146
|
+
message: "Slack token is missing. Pass `token`, inject `client`, or set SLACK_BOT_TOKEN.",
|
|
147
|
+
code: "missing-token",
|
|
148
|
+
method: "auth.test"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const methods = options.methods ?? DEFAULT_SCOPE_METHODS;
|
|
155
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
156
|
+
const args = resolved.token ? { token: resolved.token } : void 0;
|
|
157
|
+
const errors = [];
|
|
158
|
+
for (const method of methods) {
|
|
159
|
+
try {
|
|
160
|
+
const payload = await withRequestTimeout(
|
|
161
|
+
() => resolved.client.apiCall(method, args),
|
|
162
|
+
requestTimeoutMs,
|
|
163
|
+
method
|
|
164
|
+
);
|
|
165
|
+
const payloadError = slackPayloadError(payload, method);
|
|
166
|
+
if (payloadError) {
|
|
167
|
+
errors.push({
|
|
168
|
+
method,
|
|
169
|
+
error: payloadError
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const scopes = extractScopes(payload);
|
|
174
|
+
if (scopes.length > 0) {
|
|
175
|
+
return {
|
|
176
|
+
ok: true,
|
|
177
|
+
scopes,
|
|
178
|
+
source: method,
|
|
179
|
+
errors
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
errors.push({
|
|
183
|
+
method,
|
|
184
|
+
error: {
|
|
185
|
+
message: `${method} did not return scope data.`,
|
|
186
|
+
code: "no-scope-data",
|
|
187
|
+
method
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
errors.push({
|
|
192
|
+
method,
|
|
193
|
+
error: toSlackApiInspectionError(error, method)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
scopes: [],
|
|
200
|
+
errors
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async function resolveDiagnosticsClient(options) {
|
|
204
|
+
const explicitToken = normalizeToken(options.token);
|
|
205
|
+
if (options.client) {
|
|
206
|
+
return {
|
|
207
|
+
client: options.client,
|
|
208
|
+
token: explicitToken,
|
|
209
|
+
tokenSource: explicitToken ? "provided" : "client"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (explicitToken) {
|
|
213
|
+
return {
|
|
214
|
+
client: await createSlackWebClient(explicitToken),
|
|
215
|
+
token: explicitToken,
|
|
216
|
+
tokenSource: "provided"
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const envToken = normalizeToken(process.env.SLACK_BOT_TOKEN);
|
|
220
|
+
if (envToken) {
|
|
221
|
+
return {
|
|
222
|
+
client: await createSlackWebClient(envToken),
|
|
223
|
+
token: envToken,
|
|
224
|
+
tokenSource: "environment"
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return { tokenSource: "none" };
|
|
228
|
+
}
|
|
229
|
+
async function createSlackWebClient(token) {
|
|
230
|
+
const { WebClient } = await import("@slack/web-api");
|
|
231
|
+
return new WebClient(token);
|
|
232
|
+
}
|
|
233
|
+
async function withRequestTimeout(operation, timeoutMs, method) {
|
|
234
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
235
|
+
return await operation();
|
|
236
|
+
}
|
|
237
|
+
let timeoutId;
|
|
238
|
+
try {
|
|
239
|
+
return await Promise.race([
|
|
240
|
+
operation(),
|
|
241
|
+
new Promise((_resolve, reject) => {
|
|
242
|
+
timeoutId = setTimeout(() => {
|
|
243
|
+
const error = new Error(`${method} timed out after ${timeoutMs}ms.`);
|
|
244
|
+
error.code = "timeout";
|
|
245
|
+
reject(error);
|
|
246
|
+
}, timeoutMs);
|
|
247
|
+
})
|
|
248
|
+
]);
|
|
249
|
+
} finally {
|
|
250
|
+
if (timeoutId) {
|
|
251
|
+
clearTimeout(timeoutId);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function normalizeAuthInfo(payload) {
|
|
256
|
+
const record = readRecord(payload);
|
|
257
|
+
const auth = {
|
|
258
|
+
ok: readBoolean(record?.ok) ?? false
|
|
259
|
+
};
|
|
260
|
+
const url = readString(record?.url);
|
|
261
|
+
const team = readString(record?.team);
|
|
262
|
+
const teamId = readString(record?.team_id);
|
|
263
|
+
const user = readString(record?.user);
|
|
264
|
+
const userId = readString(record?.user_id);
|
|
265
|
+
const botId = readString(record?.bot_id);
|
|
266
|
+
const enterpriseId = readString(record?.enterprise_id);
|
|
267
|
+
if (url) auth.url = url;
|
|
268
|
+
if (team) auth.team = team;
|
|
269
|
+
if (teamId) auth.teamId = teamId;
|
|
270
|
+
if (user) auth.user = user;
|
|
271
|
+
if (userId) auth.userId = userId;
|
|
272
|
+
if (botId) auth.botId = botId;
|
|
273
|
+
if (enterpriseId) auth.enterpriseId = enterpriseId;
|
|
274
|
+
const isEnterpriseInstall = readBoolean(record?.is_enterprise_install);
|
|
275
|
+
if (isEnterpriseInstall !== void 0) {
|
|
276
|
+
auth.isEnterpriseInstall = isEnterpriseInstall;
|
|
277
|
+
}
|
|
278
|
+
return auth;
|
|
279
|
+
}
|
|
280
|
+
function slackPayloadError(payload, method) {
|
|
281
|
+
const record = readRecord(payload);
|
|
282
|
+
if (!record || record.ok !== false) {
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
const code = readString(record.error);
|
|
286
|
+
return {
|
|
287
|
+
message: `${method} failed${code ? `: ${code}` : ""}.`,
|
|
288
|
+
...code ? { code } : {},
|
|
289
|
+
method,
|
|
290
|
+
...readScopeErrorDetails(record)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function toSlackApiInspectionError(error, method) {
|
|
294
|
+
if (error instanceof Error) {
|
|
295
|
+
const record = error;
|
|
296
|
+
const data = readRecord(record.data);
|
|
297
|
+
const dataCode = data ? readString(data.error) : void 0;
|
|
298
|
+
const code = dataCode ?? readString(record.code);
|
|
299
|
+
return {
|
|
300
|
+
message: error.message || `${method} failed.`,
|
|
301
|
+
...code ? { code } : {},
|
|
302
|
+
method,
|
|
303
|
+
...typeof record.statusCode === "number" ? { statusCode: record.statusCode } : {},
|
|
304
|
+
...data ? readScopeErrorDetails(data) : {}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
message: typeof error === "string" ? error : `${method} failed.`,
|
|
309
|
+
method
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function readScopeErrorDetails(record) {
|
|
313
|
+
const responseMetadata = readRecord(record.response_metadata);
|
|
314
|
+
const providedScopes = extractScopes(responseMetadata?.scopes);
|
|
315
|
+
const acceptedScopes = extractScopes(responseMetadata?.acceptedScopes);
|
|
316
|
+
const needed = readString(record.needed);
|
|
317
|
+
return {
|
|
318
|
+
...needed ? { needed } : {},
|
|
319
|
+
...providedScopes.length > 0 ? { providedScopes } : {},
|
|
320
|
+
...acceptedScopes.length > 0 ? { acceptedScopes } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function extractScopes(payload) {
|
|
324
|
+
const scopes = [];
|
|
325
|
+
if (typeof payload === "string" || Array.isArray(payload)) {
|
|
326
|
+
collectScopes(payload, scopes);
|
|
327
|
+
}
|
|
328
|
+
const record = readRecord(payload);
|
|
329
|
+
if (record) {
|
|
330
|
+
collectScopes(record.scopes, scopes);
|
|
331
|
+
collectScopes(record.scope, scopes);
|
|
332
|
+
const responseMetadata = readRecord(record.response_metadata);
|
|
333
|
+
collectScopes(responseMetadata?.scopes, scopes);
|
|
334
|
+
const info = readRecord(record.info);
|
|
335
|
+
collectScopes(info?.scopes, scopes);
|
|
336
|
+
collectScopes(info?.scope, scopes);
|
|
337
|
+
collectScopes(info?.user_scopes, scopes);
|
|
338
|
+
collectScopes(info?.bot_scopes, scopes);
|
|
339
|
+
}
|
|
340
|
+
return normalizeScopes(scopes);
|
|
341
|
+
}
|
|
342
|
+
function collectScopes(value, into) {
|
|
343
|
+
if (!value) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (typeof value === "string") {
|
|
347
|
+
for (const entry of value.split(/[,\s]+/)) {
|
|
348
|
+
const normalized = entry.trim();
|
|
349
|
+
if (normalized) {
|
|
350
|
+
into.push(normalized);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (Array.isArray(value)) {
|
|
356
|
+
for (const entry of value) {
|
|
357
|
+
collectScopes(entry, into);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const record = readRecord(value);
|
|
362
|
+
if (!record) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
for (const entry of Object.values(record)) {
|
|
366
|
+
if (typeof entry === "string" || Array.isArray(entry)) {
|
|
367
|
+
collectScopes(entry, into);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function normalizeScopes(scopes) {
|
|
372
|
+
const unique = /* @__PURE__ */ new Map();
|
|
373
|
+
for (const scope of scopes) {
|
|
374
|
+
const trimmed = scope.trim();
|
|
375
|
+
if (trimmed) {
|
|
376
|
+
unique.set(trimmed.toLowerCase(), trimmed);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return [...unique.values()].sort((a, b) => a.localeCompare(b));
|
|
380
|
+
}
|
|
381
|
+
function missingScopes(expected, actual) {
|
|
382
|
+
const actualSet = new Set(actual.map((scope) => scope.toLowerCase()));
|
|
383
|
+
return expected.filter((scope) => !actualSet.has(scope.toLowerCase()));
|
|
384
|
+
}
|
|
385
|
+
function normalizeToken(token) {
|
|
386
|
+
const trimmed = token?.trim();
|
|
387
|
+
return trimmed || void 0;
|
|
388
|
+
}
|
|
389
|
+
function readRecord(value) {
|
|
390
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
391
|
+
}
|
|
392
|
+
function readString(value) {
|
|
393
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
394
|
+
}
|
|
395
|
+
function readBoolean(value) {
|
|
396
|
+
return typeof value === "boolean" ? value : void 0;
|
|
397
|
+
}
|
|
398
|
+
function elapsed(startedAt) {
|
|
399
|
+
return Date.now() - startedAt;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export {
|
|
403
|
+
inspectSlackConnection,
|
|
404
|
+
inspectSlackTokenScopes
|
|
405
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// src/shared/formatting.ts
|
|
2
|
+
function resolveSlackMessageFormatter(options) {
|
|
3
|
+
if (options.formatMessageText) {
|
|
4
|
+
return options.formatMessageText;
|
|
5
|
+
}
|
|
6
|
+
if (options.formatChatMarkdown === false) {
|
|
7
|
+
return identity;
|
|
8
|
+
}
|
|
9
|
+
return markdownToSlackMrkdwn;
|
|
10
|
+
}
|
|
11
|
+
function markdownToSlackMrkdwn(markdown) {
|
|
12
|
+
if (!markdown) {
|
|
13
|
+
return markdown;
|
|
14
|
+
}
|
|
15
|
+
const fences = [];
|
|
16
|
+
const withoutFences = markdown.replace(
|
|
17
|
+
/```[^\n`]*\n([\s\S]*?)```/g,
|
|
18
|
+
(_match, code) => {
|
|
19
|
+
const index = fences.push(`\`\`\`
|
|
20
|
+
${code}\`\`\``) - 1;
|
|
21
|
+
return `@@SLACK_FENCE_${index}@@`;
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
const boldSegments = [];
|
|
25
|
+
const withBoldPlaceholders = withoutFences.split("\n").map(formatMarkdownLine).join("\n").replace(/\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g, "<$2|$1>").replace(/\*\*([^*\n]+)\*\*/g, (_match, text) => {
|
|
26
|
+
const index = boldSegments.push(`*${text}*`) - 1;
|
|
27
|
+
return `@@SLACK_BOLD_${index}@@`;
|
|
28
|
+
}).replace(/__([^_\n]+)__/g, (_match, text) => {
|
|
29
|
+
const index = boldSegments.push(`*${text}*`) - 1;
|
|
30
|
+
return `@@SLACK_BOLD_${index}@@`;
|
|
31
|
+
});
|
|
32
|
+
const converted = withBoldPlaceholders.replace(
|
|
33
|
+
/(^|[\s([{"'])\*([^*\n][^*\n]*?)\*(?=[\s.,;:!?)}\]'"]|$)/g,
|
|
34
|
+
"$1_$2_"
|
|
35
|
+
);
|
|
36
|
+
return converted.replace(/@@SLACK_BOLD_(\d+)@@/g, (_match, rawIndex) => {
|
|
37
|
+
const index = Number(rawIndex);
|
|
38
|
+
return boldSegments[index] ?? "";
|
|
39
|
+
}).replace(/@@SLACK_FENCE_(\d+)@@/g, (_match, rawIndex) => {
|
|
40
|
+
const index = Number(rawIndex);
|
|
41
|
+
return fences[index] ?? "";
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function formatMarkdownLine(line) {
|
|
45
|
+
return line.replace(/^(\s*)#{1,6}\s+(.+?)\s*#*\s*$/, "$1**$2**");
|
|
46
|
+
}
|
|
47
|
+
function identity(text) {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/shared/turn-context.ts
|
|
52
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
53
|
+
var store = new AsyncLocalStorage();
|
|
54
|
+
function currentSlackTurnContext() {
|
|
55
|
+
return store.getStore();
|
|
56
|
+
}
|
|
57
|
+
function runWithSlackTurnContext(value, fn) {
|
|
58
|
+
return Promise.resolve(store.run({ ...value }, fn));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/shared/follow-up.ts
|
|
62
|
+
function formatSlackAttributedFollowUp(message) {
|
|
63
|
+
const slack = currentSlackTurnContext();
|
|
64
|
+
if (!slack) {
|
|
65
|
+
return message;
|
|
66
|
+
}
|
|
67
|
+
const sender = formatSlackSender(slack);
|
|
68
|
+
const conversation = formatSlackConversation(slack);
|
|
69
|
+
if (!sender && !conversation) {
|
|
70
|
+
return message;
|
|
71
|
+
}
|
|
72
|
+
const lines = ["Additional Slack follow-up for the running request."];
|
|
73
|
+
if (sender) {
|
|
74
|
+
lines.push(`Sender: ${sender}`);
|
|
75
|
+
}
|
|
76
|
+
if (conversation) {
|
|
77
|
+
lines.push(`Conversation: ${conversation}`);
|
|
78
|
+
}
|
|
79
|
+
lines.push("Message:", message);
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
function formatSlackSender(slack) {
|
|
83
|
+
const userId = slack.slackActivity.userId || slack.user.userId;
|
|
84
|
+
const displayName = readPreparedSlackDisplayName(slack.context) ?? slack.user.userName;
|
|
85
|
+
if (displayName && userId) {
|
|
86
|
+
return `${displayName} (${userId})`;
|
|
87
|
+
}
|
|
88
|
+
return displayName ?? userId ?? "unknown Slack user";
|
|
89
|
+
}
|
|
90
|
+
function formatSlackConversation(slack) {
|
|
91
|
+
const activity = slack.slackActivity;
|
|
92
|
+
const surface = formatSlackSurface(activity.channelType);
|
|
93
|
+
const parts = [
|
|
94
|
+
activity.teamId ? `team ${activity.teamId}` : void 0,
|
|
95
|
+
activity.channelId ? `channel ${activity.channelId}` : void 0,
|
|
96
|
+
activity.threadTs ? `thread ${activity.threadTs}` : activity.messageTs ? `message ${activity.messageTs}` : void 0
|
|
97
|
+
].filter((part) => Boolean(part));
|
|
98
|
+
return parts.length > 0 ? `${surface} (${parts.join(", ")})` : surface;
|
|
99
|
+
}
|
|
100
|
+
function formatSlackSurface(channelType) {
|
|
101
|
+
switch (channelType) {
|
|
102
|
+
case "dm":
|
|
103
|
+
return "private Slack DM";
|
|
104
|
+
case "thread":
|
|
105
|
+
return "shared Slack thread";
|
|
106
|
+
case "channel":
|
|
107
|
+
return "shared Slack channel";
|
|
108
|
+
case "group":
|
|
109
|
+
return "shared Slack group conversation";
|
|
110
|
+
default:
|
|
111
|
+
return "Slack conversation";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function readPreparedSlackDisplayName(context) {
|
|
115
|
+
const slack = context?.slack;
|
|
116
|
+
if (!slack || typeof slack !== "object") {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
const value = slack.userDisplayName;
|
|
120
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/shared/session.ts
|
|
124
|
+
function resolveThreadAwareSlackSessionId(info) {
|
|
125
|
+
if (info.channelType === "dm") {
|
|
126
|
+
return info.channelId;
|
|
127
|
+
}
|
|
128
|
+
if (info.threadTs) {
|
|
129
|
+
return `${info.channelId}:${info.threadTs}`;
|
|
130
|
+
}
|
|
131
|
+
return `${info.channelId}:${info.messageTs ?? info.channelId}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
resolveSlackMessageFormatter,
|
|
136
|
+
markdownToSlackMrkdwn,
|
|
137
|
+
currentSlackTurnContext,
|
|
138
|
+
runWithSlackTurnContext,
|
|
139
|
+
formatSlackAttributedFollowUp,
|
|
140
|
+
resolveThreadAwareSlackSessionId
|
|
141
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractSlackMessageText,
|
|
3
|
+
resolveSlackChannelType
|
|
4
|
+
} from "./chunk-FPCE5V5Y.js";
|
|
5
|
+
|
|
6
|
+
// src/shared/activity/parse.ts
|
|
7
|
+
function extractSlackActionToken(payload) {
|
|
8
|
+
const direct = asNonEmptyString(payload?.action_token);
|
|
9
|
+
if (direct) return direct;
|
|
10
|
+
const assistantThread = payload?.assistant_thread;
|
|
11
|
+
if (!assistantThread || typeof assistantThread !== "object") {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
return asNonEmptyString(
|
|
15
|
+
assistantThread.action_token
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function parseSlackMessageActivity(payload) {
|
|
19
|
+
const channelId = payload.channel ?? "unknown";
|
|
20
|
+
const userId = payload.user ?? "unknown";
|
|
21
|
+
const messageTs = payload.ts;
|
|
22
|
+
const threadTs = payload.thread_ts;
|
|
23
|
+
const isThread = !!threadTs && threadTs !== messageTs;
|
|
24
|
+
const channelType = resolveSlackChannelType(channelId, threadTs, isThread);
|
|
25
|
+
const text = extractSlackMessageText(payload, {
|
|
26
|
+
stripLeadingMentions: channelType !== "dm"
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
channelId,
|
|
30
|
+
channelType,
|
|
31
|
+
userId,
|
|
32
|
+
teamId: payload.team,
|
|
33
|
+
threadTs: isThread ? threadTs : void 0,
|
|
34
|
+
messageTs,
|
|
35
|
+
parentUserId: payload.parent_user_id,
|
|
36
|
+
actionToken: extractSlackActionToken(payload),
|
|
37
|
+
text,
|
|
38
|
+
isMention: false
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function parseSlackMentionActivity(payload) {
|
|
42
|
+
const channelId = payload.channel ?? "unknown";
|
|
43
|
+
const userId = payload.user ?? "unknown";
|
|
44
|
+
const messageTs = payload.ts;
|
|
45
|
+
const threadTs = payload.thread_ts;
|
|
46
|
+
const isThread = !!threadTs && threadTs !== messageTs;
|
|
47
|
+
const channelType = resolveSlackChannelType(channelId, threadTs, isThread);
|
|
48
|
+
const text = extractSlackMessageText(payload, {
|
|
49
|
+
stripLeadingMentions: true
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
channelId,
|
|
53
|
+
channelType,
|
|
54
|
+
userId,
|
|
55
|
+
teamId: payload.team,
|
|
56
|
+
threadTs: isThread ? threadTs : void 0,
|
|
57
|
+
messageTs,
|
|
58
|
+
parentUserId: payload.parent_user_id,
|
|
59
|
+
actionToken: extractSlackActionToken(payload),
|
|
60
|
+
text,
|
|
61
|
+
isMention: true
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function asNonEmptyString(value) {
|
|
65
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/shared/activity/identity.ts
|
|
69
|
+
function extractSlackUserIdentity(info) {
|
|
70
|
+
return {
|
|
71
|
+
userId: info.userId,
|
|
72
|
+
channelId: info.channelId,
|
|
73
|
+
teamId: info.teamId,
|
|
74
|
+
threadTs: info.threadTs,
|
|
75
|
+
messageTs: info.messageTs
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function extractSlackAuthContext(context, eventUserId) {
|
|
79
|
+
const ctx = context && typeof context === "object" ? context : {};
|
|
80
|
+
const pickString = (key) => {
|
|
81
|
+
const value = ctx[key];
|
|
82
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
83
|
+
};
|
|
84
|
+
const auth = {};
|
|
85
|
+
const botToken = pickString("botToken");
|
|
86
|
+
if (botToken) auth.botToken = botToken;
|
|
87
|
+
const userToken = pickString("userToken");
|
|
88
|
+
if (userToken) auth.userToken = userToken;
|
|
89
|
+
const teamId = pickString("teamId");
|
|
90
|
+
if (teamId) auth.teamId = teamId;
|
|
91
|
+
const enterpriseId = pickString("enterpriseId");
|
|
92
|
+
if (enterpriseId) auth.enterpriseId = enterpriseId;
|
|
93
|
+
const botUserId = pickString("botUserId");
|
|
94
|
+
if (botUserId) auth.botUserId = botUserId;
|
|
95
|
+
const botId = pickString("botId");
|
|
96
|
+
if (botId) auth.botId = botId;
|
|
97
|
+
const userId = pickString("userId") ?? eventUserId;
|
|
98
|
+
if (userId) auth.userId = userId;
|
|
99
|
+
return auth;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
extractSlackActionToken,
|
|
104
|
+
parseSlackMessageActivity,
|
|
105
|
+
parseSlackMentionActivity,
|
|
106
|
+
extractSlackUserIdentity,
|
|
107
|
+
extractSlackAuthContext
|
|
108
|
+
};
|