@anuma/sdk 1.1.6 → 1.2.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/dist/index.cjs +1 -0
- package/dist/index.mjs +1 -0
- package/dist/server/index.cjs +1 -0
- package/dist/server/index.mjs +1 -0
- package/dist/tools/index.cjs +412 -58
- package/dist/tools/index.d.mts +14 -3
- package/dist/tools/index.d.ts +14 -3
- package/dist/tools/index.mjs +412 -58
- package/package.json +1 -1
package/dist/tools/index.mjs
CHANGED
|
@@ -3246,6 +3246,7 @@ function createDropboxTools(getToken, requestAccess) {
|
|
|
3246
3246
|
|
|
3247
3247
|
// src/tools/slack.ts
|
|
3248
3248
|
var SLACK_PROVIDER = "slack";
|
|
3249
|
+
var SLACK_PENDING_APPROVAL_NOTE = "This Slack request couldn't be completed because Slack rate-limited the app. The Slack app is still pending Slack Marketplace approval, so these reads aren't available yet. Tell the user this is a temporary limitation until the app is approved; do not attempt a workaround.";
|
|
3249
3250
|
var AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3250
3251
|
"not_authed",
|
|
3251
3252
|
"invalid_auth",
|
|
@@ -3269,8 +3270,10 @@ function maybeConnectorError4(status, body) {
|
|
|
3269
3270
|
}
|
|
3270
3271
|
return null;
|
|
3271
3272
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3273
|
+
function isRateLimited(status, body) {
|
|
3274
|
+
return status === 429 || body?.ok === false && body.error === "ratelimited";
|
|
3275
|
+
}
|
|
3276
|
+
function interpretSlackResult(path, status, json) {
|
|
3274
3277
|
const body = json ?? null;
|
|
3275
3278
|
if (status < 200 || status >= 300) {
|
|
3276
3279
|
const connectorError = maybeConnectorError4(status, body);
|
|
@@ -3284,6 +3287,10 @@ async function callSlack(callProxy, path, query, requestBody) {
|
|
|
3284
3287
|
}
|
|
3285
3288
|
return body;
|
|
3286
3289
|
}
|
|
3290
|
+
async function callSlack(callProxy, path, query, requestBody) {
|
|
3291
|
+
const { status, json } = await callProxy(path, query, requestBody);
|
|
3292
|
+
return interpretSlackResult(path, status, json);
|
|
3293
|
+
}
|
|
3287
3294
|
function readString(raw) {
|
|
3288
3295
|
return typeof raw === "string" ? raw : "";
|
|
3289
3296
|
}
|
|
@@ -3292,6 +3299,70 @@ function clampLimit(raw, fallback, min, max) {
|
|
|
3292
3299
|
const safe = typeof num === "number" && Number.isFinite(num) ? num : fallback;
|
|
3293
3300
|
return Math.min(max, Math.max(min, safe));
|
|
3294
3301
|
}
|
|
3302
|
+
function makeGetAuthUserId(callProxy) {
|
|
3303
|
+
let cached;
|
|
3304
|
+
return async () => {
|
|
3305
|
+
if (cached === void 0) {
|
|
3306
|
+
const auth = await callSlack(callProxy, "/auth.test");
|
|
3307
|
+
cached = typeof auth === "string" ? null : auth.user_id ?? null;
|
|
3308
|
+
}
|
|
3309
|
+
return cached;
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
function displayNameForUser(user) {
|
|
3313
|
+
return user.profile?.display_name || user.real_name || user.name || user.id;
|
|
3314
|
+
}
|
|
3315
|
+
function authorName(m, directory) {
|
|
3316
|
+
const known = m.user ? directory.byId.get(m.user) : void 0;
|
|
3317
|
+
return known && displayNameForUser(known) || m.username || m.user || "";
|
|
3318
|
+
}
|
|
3319
|
+
var USER_MENTION_RE = /<@([UW][A-Z0-9]+)(?:\|([^>]+))?>/g;
|
|
3320
|
+
var CHANNEL_MENTION_RE = /<#(C[A-Z0-9]+)(?:\|([^>]+))?>/g;
|
|
3321
|
+
function humanizeSlackText(text, directory) {
|
|
3322
|
+
return text.replace(USER_MENTION_RE, (_match, id, handle) => {
|
|
3323
|
+
const known = directory.byId.get(id);
|
|
3324
|
+
return `@${known && displayNameForUser(known) || handle || id}`;
|
|
3325
|
+
}).replace(
|
|
3326
|
+
CHANNEL_MENTION_RE,
|
|
3327
|
+
(match, _id, name) => name ? `#${name}` : match
|
|
3328
|
+
);
|
|
3329
|
+
}
|
|
3330
|
+
var MAX_USERS_PAGES = 10;
|
|
3331
|
+
async function listAllUsers(callProxy) {
|
|
3332
|
+
const all = [];
|
|
3333
|
+
let cursor = "";
|
|
3334
|
+
for (let page = 0; page < MAX_USERS_PAGES; page++) {
|
|
3335
|
+
const query = { limit: 1e3 };
|
|
3336
|
+
if (cursor) query.cursor = cursor;
|
|
3337
|
+
const res = await callSlack(callProxy, "/users.list", query);
|
|
3338
|
+
if (typeof res === "string") {
|
|
3339
|
+
if (page === 0) return res;
|
|
3340
|
+
break;
|
|
3341
|
+
}
|
|
3342
|
+
all.push(...res.members ?? []);
|
|
3343
|
+
const next = res.response_metadata?.next_cursor;
|
|
3344
|
+
if (!next) break;
|
|
3345
|
+
cursor = next;
|
|
3346
|
+
}
|
|
3347
|
+
return all;
|
|
3348
|
+
}
|
|
3349
|
+
function makeGetUsersDirectory(callProxy) {
|
|
3350
|
+
let cached;
|
|
3351
|
+
return async () => {
|
|
3352
|
+
if (cached === void 0) {
|
|
3353
|
+
const res = await listAllUsers(callProxy);
|
|
3354
|
+
const members = (typeof res === "string" ? [] : res).filter((u) => !u.deleted);
|
|
3355
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3356
|
+
const byHandle = /* @__PURE__ */ new Map();
|
|
3357
|
+
for (const user of members) {
|
|
3358
|
+
byId.set(user.id, user);
|
|
3359
|
+
if (user.name) byHandle.set(user.name.toLowerCase(), user);
|
|
3360
|
+
}
|
|
3361
|
+
cached = { members, byId, byHandle };
|
|
3362
|
+
}
|
|
3363
|
+
return cached;
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3295
3366
|
async function getSlackMe(callProxy) {
|
|
3296
3367
|
const auth = await callSlack(callProxy, "/auth.test");
|
|
3297
3368
|
if (typeof auth === "string") return auth;
|
|
@@ -3314,14 +3385,10 @@ async function getSlackMe(callProxy) {
|
|
|
3314
3385
|
};
|
|
3315
3386
|
}
|
|
3316
3387
|
async function listSlackChannels(callProxy, args) {
|
|
3317
|
-
const limit = clampLimit(args.limit,
|
|
3318
|
-
const res = await
|
|
3319
|
-
limit,
|
|
3320
|
-
exclude_archived: "true",
|
|
3321
|
-
types: "public_channel,private_channel"
|
|
3322
|
-
});
|
|
3388
|
+
const limit = clampLimit(args.limit, 1e3, 1, 5e3);
|
|
3389
|
+
const res = await listAllConversations(callProxy, "public_channel,private_channel");
|
|
3323
3390
|
if (typeof res === "string") return res;
|
|
3324
|
-
return
|
|
3391
|
+
return res.slice(0, limit).map((c) => ({
|
|
3325
3392
|
id: c.id,
|
|
3326
3393
|
name: c.name,
|
|
3327
3394
|
is_private: c.is_private,
|
|
@@ -3330,8 +3397,150 @@ async function listSlackChannels(callProxy, args) {
|
|
|
3330
3397
|
purpose: c.purpose?.value
|
|
3331
3398
|
}));
|
|
3332
3399
|
}
|
|
3400
|
+
var MAX_DM_PROBES = 50;
|
|
3401
|
+
async function openSlackDm(callProxy, userId) {
|
|
3402
|
+
const res = await callSlack(
|
|
3403
|
+
callProxy,
|
|
3404
|
+
"/conversations.open",
|
|
3405
|
+
void 0,
|
|
3406
|
+
{ users: userId }
|
|
3407
|
+
);
|
|
3408
|
+
if (typeof res === "string") return null;
|
|
3409
|
+
return res.channel?.id ?? null;
|
|
3410
|
+
}
|
|
3411
|
+
async function dmHasMessages(callProxy, channelId) {
|
|
3412
|
+
const { status, json } = await callProxy("/conversations.history", {
|
|
3413
|
+
channel: channelId,
|
|
3414
|
+
limit: 1
|
|
3415
|
+
});
|
|
3416
|
+
const body = json ?? null;
|
|
3417
|
+
if (isRateLimited(status, body)) return true;
|
|
3418
|
+
const res = interpretSlackResult(
|
|
3419
|
+
"/conversations.history",
|
|
3420
|
+
status,
|
|
3421
|
+
json
|
|
3422
|
+
);
|
|
3423
|
+
if (typeof res === "string") return true;
|
|
3424
|
+
return (res.messages ?? []).length > 0;
|
|
3425
|
+
}
|
|
3426
|
+
async function listSlackDms(callProxy, args) {
|
|
3427
|
+
const res = await listAllConversations(callProxy, "im,mpim");
|
|
3428
|
+
if (typeof res === "string") return res;
|
|
3429
|
+
const getAuthUserId = makeGetAuthUserId(callProxy);
|
|
3430
|
+
const getUsersDirectory = makeGetUsersDirectory(callProxy);
|
|
3431
|
+
const dmNameCache = /* @__PURE__ */ new Map();
|
|
3432
|
+
const buildListing = async (convs) => {
|
|
3433
|
+
const listing = { direct_messages: [], group_dms: [] };
|
|
3434
|
+
for (const conv of convs) {
|
|
3435
|
+
if (conv.is_mpim) {
|
|
3436
|
+
const [directory, selfId] = await Promise.all([getUsersDirectory(), getAuthUserId()]);
|
|
3437
|
+
const members = groupDmMembers(conv.name, directory, selfId) ?? formatGroupDmLabel(conv.name, directory, selfId);
|
|
3438
|
+
listing.group_dms.push({ id: conv.id, members });
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const name = await labelForChannel(
|
|
3442
|
+
callProxy,
|
|
3443
|
+
conv,
|
|
3444
|
+
dmNameCache,
|
|
3445
|
+
getAuthUserId,
|
|
3446
|
+
getUsersDirectory
|
|
3447
|
+
);
|
|
3448
|
+
listing.direct_messages.push({ id: conv.id, name });
|
|
3449
|
+
}
|
|
3450
|
+
return listing;
|
|
3451
|
+
};
|
|
3452
|
+
const withUserRef = (args.with_user ?? "").trim();
|
|
3453
|
+
if (withUserRef) {
|
|
3454
|
+
const targetId = await resolveSlackUserId(withUserRef, getAuthUserId, getUsersDirectory);
|
|
3455
|
+
if (!targetId) return `Error: couldn't find a Slack user matching "${withUserRef}".`;
|
|
3456
|
+
const directory = await getUsersDirectory();
|
|
3457
|
+
const targetHandle = directory.byId.get(targetId)?.name?.toLowerCase();
|
|
3458
|
+
const matches = res.filter((conv) => {
|
|
3459
|
+
if (conv.is_mpim) {
|
|
3460
|
+
return targetHandle ? parseMpdmHandles(conv.name).some((h) => h.toLowerCase() === targetHandle) : false;
|
|
3461
|
+
}
|
|
3462
|
+
return conv.user === targetId;
|
|
3463
|
+
});
|
|
3464
|
+
const targetLimit = clampLimit(args.limit, matches.length, 1, matches.length);
|
|
3465
|
+
const listing = await buildListing(matches.slice(0, targetLimit));
|
|
3466
|
+
if (listing.direct_messages.length === 0) {
|
|
3467
|
+
const openedId = await openSlackDm(callProxy, targetId);
|
|
3468
|
+
if (openedId) {
|
|
3469
|
+
const surface = listing.group_dms.length === 0 || await dmHasMessages(callProxy, openedId);
|
|
3470
|
+
if (surface) {
|
|
3471
|
+
const counterparty = directory.byId.get(targetId);
|
|
3472
|
+
listing.direct_messages.push({
|
|
3473
|
+
id: openedId,
|
|
3474
|
+
name: counterparty ? displayNameForUser(counterparty) : targetId
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return listing;
|
|
3480
|
+
}
|
|
3481
|
+
const limit = clampLimit(args.limit, 5, 1, MAX_DM_PROBES);
|
|
3482
|
+
if (res.length > MAX_DM_PROBES) return SLACK_PENDING_APPROVAL_NOTE;
|
|
3483
|
+
const withTs = [];
|
|
3484
|
+
const withoutTs = [];
|
|
3485
|
+
for (const conv of res) {
|
|
3486
|
+
const { status, json } = await callProxy("/conversations.history", {
|
|
3487
|
+
channel: conv.id,
|
|
3488
|
+
limit: 1
|
|
3489
|
+
});
|
|
3490
|
+
const body = json ?? null;
|
|
3491
|
+
if (isRateLimited(status, body)) return SLACK_PENDING_APPROVAL_NOTE;
|
|
3492
|
+
if (status < 200 || status >= 300 || !body || body.ok === false) {
|
|
3493
|
+
withoutTs.push(conv);
|
|
3494
|
+
continue;
|
|
3495
|
+
}
|
|
3496
|
+
const messages = body.messages ?? [];
|
|
3497
|
+
if (messages.length === 0) continue;
|
|
3498
|
+
withTs.push({ conv, lastTs: Number(messages[0].ts) });
|
|
3499
|
+
}
|
|
3500
|
+
withTs.sort((a, b) => b.lastTs - a.lastTs);
|
|
3501
|
+
const ordered = [...withTs.map((x) => x.conv), ...withoutTs];
|
|
3502
|
+
return buildListing(ordered.slice(0, limit));
|
|
3503
|
+
}
|
|
3333
3504
|
var MAX_SEARCH_CHANNELS = 8;
|
|
3334
3505
|
var SEARCH_HISTORY_LIMIT = 15;
|
|
3506
|
+
var MAX_CONVERSATIONS_PAGES = 10;
|
|
3507
|
+
async function listAllConversations(callProxy, types) {
|
|
3508
|
+
const all = [];
|
|
3509
|
+
let cursor = "";
|
|
3510
|
+
for (let page = 0; page < MAX_CONVERSATIONS_PAGES; page++) {
|
|
3511
|
+
const query = {
|
|
3512
|
+
limit: 1e3,
|
|
3513
|
+
exclude_archived: "true",
|
|
3514
|
+
types
|
|
3515
|
+
};
|
|
3516
|
+
if (cursor) query.cursor = cursor;
|
|
3517
|
+
const res = await callSlack(
|
|
3518
|
+
callProxy,
|
|
3519
|
+
"/conversations.list",
|
|
3520
|
+
query
|
|
3521
|
+
);
|
|
3522
|
+
if (typeof res === "string") {
|
|
3523
|
+
if (page === 0) return res;
|
|
3524
|
+
break;
|
|
3525
|
+
}
|
|
3526
|
+
all.push(...res.channels ?? []);
|
|
3527
|
+
const next = res.response_metadata?.next_cursor;
|
|
3528
|
+
if (!next) break;
|
|
3529
|
+
cursor = next;
|
|
3530
|
+
}
|
|
3531
|
+
return all;
|
|
3532
|
+
}
|
|
3533
|
+
async function resolveChannelId(callProxy, ref) {
|
|
3534
|
+
const trimmed = ref.trim().replace(/^#/, "");
|
|
3535
|
+
if (SLACK_CHANNEL_ID_RE.test(trimmed)) return { id: trimmed };
|
|
3536
|
+
const channels = await listAllConversations(callProxy, "public_channel,private_channel");
|
|
3537
|
+
if (typeof channels === "string") return channels;
|
|
3538
|
+
const match = channels.find((c) => c.name?.toLowerCase() === trimmed.toLowerCase());
|
|
3539
|
+
if (!match) {
|
|
3540
|
+
return `Error: no Slack channel named "${ref}" in this workspace (checked the channels you're a member of).`;
|
|
3541
|
+
}
|
|
3542
|
+
return { id: match.id };
|
|
3543
|
+
}
|
|
3335
3544
|
function interleaveChannelsAndDms(conversations) {
|
|
3336
3545
|
const channels = conversations.filter((c) => !c.is_im && !c.is_mpim);
|
|
3337
3546
|
const dms = conversations.filter((c) => c.is_im || c.is_mpim);
|
|
@@ -3347,8 +3556,40 @@ function messageMatchesQuery(text, terms) {
|
|
|
3347
3556
|
const haystack = text.toLowerCase();
|
|
3348
3557
|
return terms.every((term) => haystack.includes(term));
|
|
3349
3558
|
}
|
|
3350
|
-
|
|
3351
|
-
|
|
3559
|
+
var MAX_GROUP_DM_NAMES = 5;
|
|
3560
|
+
var MPDM_NAME_RE = /^mpdm-(.+)-\d+$/;
|
|
3561
|
+
function parseMpdmHandles(mpimName) {
|
|
3562
|
+
if (!mpimName) return [];
|
|
3563
|
+
const match = MPDM_NAME_RE.exec(mpimName);
|
|
3564
|
+
return match ? match[1].split("--") : [];
|
|
3565
|
+
}
|
|
3566
|
+
function groupDmMembers(mpimName, directory, selfId) {
|
|
3567
|
+
const handles = parseMpdmHandles(mpimName);
|
|
3568
|
+
if (handles.length === 0) return null;
|
|
3569
|
+
const selfHandle = selfId ? directory.byId.get(selfId)?.name?.toLowerCase() : void 0;
|
|
3570
|
+
const names = [];
|
|
3571
|
+
for (const handle of handles) {
|
|
3572
|
+
const key = handle.toLowerCase();
|
|
3573
|
+
if (selfHandle && key === selfHandle) continue;
|
|
3574
|
+
const member = directory.byHandle.get(key);
|
|
3575
|
+
names.push(member ? displayNameForUser(member) : handle);
|
|
3576
|
+
}
|
|
3577
|
+
if (names.length === 0) return null;
|
|
3578
|
+
const shown = names.slice(0, MAX_GROUP_DM_NAMES);
|
|
3579
|
+
const extra = names.length - shown.length;
|
|
3580
|
+
return `${shown.join(", ")}${extra > 0 ? `, +${extra} more` : ""}`;
|
|
3581
|
+
}
|
|
3582
|
+
function formatGroupDmLabel(mpimName, directory, selfId) {
|
|
3583
|
+
if (!mpimName) return "Group DM";
|
|
3584
|
+
if (parseMpdmHandles(mpimName).length === 0) return mpimName;
|
|
3585
|
+
const members = groupDmMembers(mpimName, directory, selfId);
|
|
3586
|
+
return members === null ? "Group DM" : `Group DM with ${members}`;
|
|
3587
|
+
}
|
|
3588
|
+
async function labelForChannel(callProxy, channel, dmNameCache, getAuthUserId, getUsersDirectory) {
|
|
3589
|
+
if (channel.is_mpim) {
|
|
3590
|
+
const [directory2, selfId] = await Promise.all([getUsersDirectory(), getAuthUserId()]);
|
|
3591
|
+
return formatGroupDmLabel(channel.name, directory2, selfId);
|
|
3592
|
+
}
|
|
3352
3593
|
if (!channel.is_im) return channel.name ?? channel.id;
|
|
3353
3594
|
const otherId = channel.user;
|
|
3354
3595
|
if (!otherId) return channel.id;
|
|
@@ -3356,72 +3597,130 @@ async function labelForChannel(callProxy, channel, dmNameCache, getAuthUserId) {
|
|
|
3356
3597
|
if (authUserId && otherId === authUserId) return "Direct message";
|
|
3357
3598
|
const cached = dmNameCache.get(otherId);
|
|
3358
3599
|
if (cached) return `DM with ${cached}`;
|
|
3600
|
+
const directory = await getUsersDirectory();
|
|
3601
|
+
const known = directory.byId.get(otherId);
|
|
3602
|
+
if (known) {
|
|
3603
|
+
const name2 = displayNameForUser(known);
|
|
3604
|
+
dmNameCache.set(otherId, name2);
|
|
3605
|
+
return `DM with ${name2}`;
|
|
3606
|
+
}
|
|
3359
3607
|
const info = await callSlack(callProxy, "/users.info", { user: otherId });
|
|
3360
3608
|
if (typeof info === "string" || !info.user) return channel.id;
|
|
3361
3609
|
const name = info.user.profile?.display_name || info.user.real_name || info.user.name || otherId;
|
|
3362
3610
|
dmNameCache.set(otherId, name);
|
|
3363
3611
|
return `DM with ${name}`;
|
|
3364
3612
|
}
|
|
3613
|
+
var SLACK_USER_ID_RE = /^[UW][A-Z0-9]{6,}$/;
|
|
3614
|
+
var SLACK_CHANNEL_ID_RE = /^[CDG][A-Z0-9]{6,}$/;
|
|
3615
|
+
var SELF_REFS = /* @__PURE__ */ new Set(["me", "myself", "self", "i"]);
|
|
3616
|
+
async function resolveSlackUserId(ref, getAuthUserId, getUsersDirectory) {
|
|
3617
|
+
const trimmed = ref.trim();
|
|
3618
|
+
if (!trimmed) return null;
|
|
3619
|
+
if (SELF_REFS.has(trimmed.toLowerCase())) return getAuthUserId();
|
|
3620
|
+
if (SLACK_USER_ID_RE.test(trimmed)) return trimmed;
|
|
3621
|
+
const needle = trimmed.replace(/^@/, "").toLowerCase();
|
|
3622
|
+
if (!needle) return null;
|
|
3623
|
+
const { members } = await getUsersDirectory();
|
|
3624
|
+
const fieldsOf = (u) => [u.name, u.real_name, u.profile?.display_name].filter((f) => Boolean(f));
|
|
3625
|
+
const matching = (pred) => members.filter((u) => fieldsOf(u).some((f) => pred(f.toLowerCase())));
|
|
3626
|
+
const exact = matching((f) => f === needle);
|
|
3627
|
+
if (exact.length === 1) return exact[0].id;
|
|
3628
|
+
if (exact.length > 1) return null;
|
|
3629
|
+
const partial = matching((f) => f.includes(needle));
|
|
3630
|
+
return partial.length === 1 ? partial[0].id : null;
|
|
3631
|
+
}
|
|
3365
3632
|
async function searchSlackMessages(callProxy, args) {
|
|
3633
|
+
const query = (args.query ?? "").trim();
|
|
3634
|
+
const fromUserRef = (args.from_user ?? "").trim();
|
|
3635
|
+
const mentionsRef = (args.mentions ?? "").trim();
|
|
3636
|
+
if (!query && !fromUserRef && !mentionsRef) {
|
|
3637
|
+
return "Error: provide a query, from_user, or mentions to search Slack messages.";
|
|
3638
|
+
}
|
|
3366
3639
|
const count = clampLimit(args.count, 20, 1, 100);
|
|
3367
|
-
const terms =
|
|
3368
|
-
const
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3640
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
3641
|
+
const getAuthUserId = makeGetAuthUserId(callProxy);
|
|
3642
|
+
const getUsersDirectory = makeGetUsersDirectory(callProxy);
|
|
3643
|
+
let fromId = null;
|
|
3644
|
+
if (fromUserRef) {
|
|
3645
|
+
fromId = await resolveSlackUserId(fromUserRef, getAuthUserId, getUsersDirectory);
|
|
3646
|
+
if (!fromId)
|
|
3647
|
+
return `Error: couldn't find a Slack user matching "${fromUserRef}" for from_user.`;
|
|
3648
|
+
}
|
|
3649
|
+
let mentionId = null;
|
|
3650
|
+
if (mentionsRef) {
|
|
3651
|
+
mentionId = await resolveSlackUserId(mentionsRef, getAuthUserId, getUsersDirectory);
|
|
3652
|
+
if (!mentionId) {
|
|
3653
|
+
return `Error: couldn't find a Slack user matching "${mentionsRef}" for mentions.`;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
const passes = (m) => {
|
|
3657
|
+
const text = m.text ?? "";
|
|
3658
|
+
if (terms.length > 0 && !messageMatchesQuery(text, terms)) return false;
|
|
3659
|
+
if (fromId && m.user !== fromId) return false;
|
|
3660
|
+
if (mentionId && !text.includes("<@" + mentionId)) return false;
|
|
3661
|
+
return true;
|
|
3662
|
+
};
|
|
3663
|
+
const listRes = await listAllConversations(callProxy, "public_channel,private_channel,im,mpim");
|
|
3377
3664
|
if (typeof listRes === "string") return listRes;
|
|
3378
|
-
const allChannels = listRes
|
|
3665
|
+
const allChannels = listRes;
|
|
3379
3666
|
let targets;
|
|
3380
3667
|
if (args.channel) {
|
|
3381
|
-
const
|
|
3382
|
-
|
|
3668
|
+
const raw = args.channel.trim();
|
|
3669
|
+
const bare = raw.replace(/^#/, "");
|
|
3670
|
+
const found = allChannels.find(
|
|
3671
|
+
(c) => c.id === raw || c.name?.toLowerCase() === bare.toLowerCase()
|
|
3672
|
+
);
|
|
3673
|
+
if (found) {
|
|
3674
|
+
targets = [found];
|
|
3675
|
+
} else if (SLACK_CHANNEL_ID_RE.test(bare)) {
|
|
3676
|
+
targets = [{ id: bare, name: bare }];
|
|
3677
|
+
} else {
|
|
3678
|
+
return `Error: no Slack channel named "${args.channel}" in this workspace.`;
|
|
3679
|
+
}
|
|
3383
3680
|
} else {
|
|
3384
3681
|
targets = interleaveChannelsAndDms(allChannels).slice(0, MAX_SEARCH_CHANNELS);
|
|
3385
3682
|
}
|
|
3386
|
-
let cachedAuthUserId;
|
|
3387
|
-
const getAuthUserId = async () => {
|
|
3388
|
-
if (cachedAuthUserId === void 0) {
|
|
3389
|
-
const auth = await callSlack(callProxy, "/auth.test");
|
|
3390
|
-
cachedAuthUserId = typeof auth === "string" ? null : auth.user_id ?? null;
|
|
3391
|
-
}
|
|
3392
|
-
return cachedAuthUserId;
|
|
3393
|
-
};
|
|
3394
3683
|
const dmNameCache = /* @__PURE__ */ new Map();
|
|
3395
3684
|
const results = [];
|
|
3396
3685
|
let rateLimited = false;
|
|
3397
3686
|
for (const channel of targets) {
|
|
3398
|
-
if (results.length >= count) break;
|
|
3399
3687
|
const { status, json } = await callProxy("/conversations.history", {
|
|
3400
3688
|
channel: channel.id,
|
|
3401
3689
|
limit: SEARCH_HISTORY_LIMIT
|
|
3402
3690
|
});
|
|
3403
3691
|
const body = json ?? null;
|
|
3404
|
-
if (status
|
|
3692
|
+
if (isRateLimited(status, body)) {
|
|
3405
3693
|
rateLimited = true;
|
|
3406
3694
|
break;
|
|
3407
3695
|
}
|
|
3408
3696
|
const connectorError = maybeConnectorError4(status, body);
|
|
3409
3697
|
if (connectorError) return connectorError;
|
|
3410
3698
|
if (status < 200 || status >= 300 || !body || body.ok === false) continue;
|
|
3411
|
-
const matched = (body.messages ?? []).filter(
|
|
3699
|
+
const matched = (body.messages ?? []).filter(passes);
|
|
3412
3700
|
if (matched.length === 0) continue;
|
|
3413
|
-
const label = await labelForChannel(
|
|
3701
|
+
const label = await labelForChannel(
|
|
3702
|
+
callProxy,
|
|
3703
|
+
channel,
|
|
3704
|
+
dmNameCache,
|
|
3705
|
+
getAuthUserId,
|
|
3706
|
+
getUsersDirectory
|
|
3707
|
+
);
|
|
3708
|
+
const directory = await getUsersDirectory();
|
|
3414
3709
|
for (const m of matched) {
|
|
3415
|
-
|
|
3416
|
-
|
|
3710
|
+
results.push({
|
|
3711
|
+
text: humanizeSlackText(m.text ?? "", directory),
|
|
3712
|
+
user: authorName(m, directory),
|
|
3713
|
+
ts: m.ts,
|
|
3714
|
+
channel: label
|
|
3715
|
+
});
|
|
3417
3716
|
}
|
|
3418
3717
|
}
|
|
3718
|
+
results.sort((a, b) => Number(b.ts) - Number(a.ts));
|
|
3719
|
+
const sliced = results.slice(0, count);
|
|
3419
3720
|
if (rateLimited) {
|
|
3420
|
-
|
|
3421
|
-
note: `Slack rate-limited channel history, so this search stopped early \u2014 returning ${results.length} match(es) found so far. Try a specific channel or ask again in a minute.`
|
|
3422
|
-
});
|
|
3721
|
+
sliced.push({ note: SLACK_PENDING_APPROVAL_NOTE });
|
|
3423
3722
|
}
|
|
3424
|
-
return
|
|
3723
|
+
return sliced;
|
|
3425
3724
|
}
|
|
3426
3725
|
async function listSlackUsers(callProxy, args) {
|
|
3427
3726
|
const limit = clampLimit(args.limit, 100, 1, 1e3);
|
|
@@ -3437,29 +3736,46 @@ async function listSlackUsers(callProxy, args) {
|
|
|
3437
3736
|
}
|
|
3438
3737
|
async function getSlackChannelHistory(callProxy, args) {
|
|
3439
3738
|
const limit = clampLimit(args.limit, 20, 1, 100);
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3739
|
+
const resolved = await resolveChannelId(callProxy, args.channel);
|
|
3740
|
+
if (typeof resolved === "string") return resolved;
|
|
3741
|
+
const { status, json } = await callProxy("/conversations.history", {
|
|
3742
|
+
channel: resolved.id,
|
|
3743
|
+
limit
|
|
3744
|
+
});
|
|
3745
|
+
const body = json ?? null;
|
|
3746
|
+
if (isRateLimited(status, body)) return SLACK_PENDING_APPROVAL_NOTE;
|
|
3747
|
+
const res = interpretSlackResult(
|
|
3442
3748
|
"/conversations.history",
|
|
3443
|
-
|
|
3749
|
+
status,
|
|
3750
|
+
json
|
|
3444
3751
|
);
|
|
3445
3752
|
if (typeof res === "string") return res;
|
|
3753
|
+
const directory = await makeGetUsersDirectory(callProxy)();
|
|
3446
3754
|
return (res.messages ?? []).map((m) => ({
|
|
3447
|
-
text: m.text,
|
|
3448
|
-
user: m
|
|
3755
|
+
text: humanizeSlackText(m.text ?? "", directory),
|
|
3756
|
+
user: authorName(m, directory),
|
|
3449
3757
|
ts: m.ts
|
|
3450
3758
|
}));
|
|
3451
3759
|
}
|
|
3452
3760
|
async function getSlackThreadReplies(callProxy, args) {
|
|
3453
3761
|
const limit = clampLimit(args.limit, 20, 1, 100);
|
|
3454
|
-
const
|
|
3455
|
-
|
|
3762
|
+
const { status, json } = await callProxy("/conversations.replies", {
|
|
3763
|
+
channel: args.channel,
|
|
3764
|
+
ts: args.ts,
|
|
3765
|
+
limit
|
|
3766
|
+
});
|
|
3767
|
+
const body = json ?? null;
|
|
3768
|
+
if (isRateLimited(status, body)) return SLACK_PENDING_APPROVAL_NOTE;
|
|
3769
|
+
const res = interpretSlackResult(
|
|
3456
3770
|
"/conversations.replies",
|
|
3457
|
-
|
|
3771
|
+
status,
|
|
3772
|
+
json
|
|
3458
3773
|
);
|
|
3459
3774
|
if (typeof res === "string") return res;
|
|
3775
|
+
const directory = await makeGetUsersDirectory(callProxy)();
|
|
3460
3776
|
return (res.messages ?? []).map((m) => ({
|
|
3461
|
-
text: m.text,
|
|
3462
|
-
user: m
|
|
3777
|
+
text: humanizeSlackText(m.text ?? "", directory),
|
|
3778
|
+
user: authorName(m, directory),
|
|
3463
3779
|
ts: m.ts
|
|
3464
3780
|
}));
|
|
3465
3781
|
}
|
|
@@ -3494,7 +3810,7 @@ function createSlackListChannelsTool(callProxy) {
|
|
|
3494
3810
|
properties: {
|
|
3495
3811
|
limit: {
|
|
3496
3812
|
type: "number",
|
|
3497
|
-
description: "Max channels to return. Between 1 and
|
|
3813
|
+
description: "Max channels to return. Between 1 and 5000 (clamped). Defaults to 1000."
|
|
3498
3814
|
}
|
|
3499
3815
|
},
|
|
3500
3816
|
required: []
|
|
@@ -3508,13 +3824,13 @@ function createSlackSearchMessagesTool(callProxy) {
|
|
|
3508
3824
|
type: "function",
|
|
3509
3825
|
function: {
|
|
3510
3826
|
name: "slack_search_messages",
|
|
3511
|
-
description: "Search recent messages
|
|
3827
|
+
description: "Search recent messages across the user's Slack channels and direct messages, or within a single channel when 'channel' is given. Filter by any combination of 'query' (words the text must contain), 'from_user' (the author), and 'mentions' (who the message @-mentions) -- at least one is required. Only recent history is scanned (a bounded set of conversations), so this surfaces recent messages rather than the full archive. Use this to find messages by KEYWORD or TOPIC across conversations. Do NOT use it to read the latest/last message from a specific person or channel, or to read a specific conversation you already know: for the last message from a person, call slack_list_dms with with_user to get the DM id then slack_get_channel_history on it; for a channel, call slack_get_channel_history directly. Search fans out across many conversations and is heavily rate-limited, so a single targeted read is faster and more reliable.",
|
|
3512
3828
|
parameters: {
|
|
3513
3829
|
type: "object",
|
|
3514
3830
|
properties: {
|
|
3515
3831
|
query: {
|
|
3516
3832
|
type: "string",
|
|
3517
|
-
description: "Words to look for
|
|
3833
|
+
description: "Optional. Words to look for; a message matches when its text contains all of them (case-insensitive). Provide this, from_user, or mentions."
|
|
3518
3834
|
},
|
|
3519
3835
|
count: {
|
|
3520
3836
|
type: "number",
|
|
@@ -3523,15 +3839,52 @@ function createSlackSearchMessagesTool(callProxy) {
|
|
|
3523
3839
|
channel: {
|
|
3524
3840
|
type: "string",
|
|
3525
3841
|
description: "Optional channel id or name to search within. Omit to search across your channels."
|
|
3842
|
+
},
|
|
3843
|
+
from_user: {
|
|
3844
|
+
type: "string",
|
|
3845
|
+
description: "Optional. The message author to filter by, as a Slack user id or a name/handle."
|
|
3846
|
+
},
|
|
3847
|
+
mentions: {
|
|
3848
|
+
type: "string",
|
|
3849
|
+
description: 'Optional. Keep only messages that @-mention this person, as a user id, a name/handle, or "me" for yourself.'
|
|
3526
3850
|
}
|
|
3527
3851
|
},
|
|
3528
|
-
required: [
|
|
3852
|
+
required: []
|
|
3529
3853
|
}
|
|
3530
3854
|
},
|
|
3531
3855
|
executor: async (args) => searchSlackMessages(callProxy, {
|
|
3532
3856
|
query: readString(args.query),
|
|
3533
3857
|
count: args.count,
|
|
3534
|
-
channel: typeof args.channel === "string" ? args.channel : void 0
|
|
3858
|
+
channel: typeof args.channel === "string" ? args.channel : void 0,
|
|
3859
|
+
from_user: typeof args.from_user === "string" ? args.from_user : void 0,
|
|
3860
|
+
mentions: typeof args.mentions === "string" ? args.mentions : void 0
|
|
3861
|
+
})
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
function createSlackListDmsTool(callProxy) {
|
|
3865
|
+
return {
|
|
3866
|
+
type: "function",
|
|
3867
|
+
function: {
|
|
3868
|
+
name: "slack_list_dms",
|
|
3869
|
+
description: "List the user's Slack direct messages and group DMs, or find the DM with a specific person. Returns two separate lists: 'direct_messages' (1:1 conversations, each with 'name' = the other person) and 'group_dms' (group conversations, each with 'members' = the member names). When showing these to the user, present them as TWO separate bulleted lists -- a 'Direct messages' list (by the other person's name) and a 'Group DMs' list (by the member names) -- by name only, and never show the conversation ids. To show or read the conversation with one person, call this with with_user=<name or id> to get that conversation's id, then call slack_get_channel_history with that id to read the messages. For the latest message or recent messages from a specific person, the flow is: call this with with_user, take the returned id, then call slack_get_channel_history on it (use limit: 1 for just the last message). Do NOT use slack_search_messages to find a person's DM conversation or to read their latest message -- use with_user here instead. Listing all DMs (no with_user) returns the 5 MOST RECENT by default (most recent first, like the Slack sidebar, with empty conversations excluded), split across 'direct_messages' and 'group_dms'; pass a larger 'limit' to get more. The conversation id is only for passing to slack_get_channel_history to read a conversation.",
|
|
3870
|
+
parameters: {
|
|
3871
|
+
type: "object",
|
|
3872
|
+
properties: {
|
|
3873
|
+
limit: {
|
|
3874
|
+
type: "number",
|
|
3875
|
+
description: "Max DMs to return, default 5, up to 50."
|
|
3876
|
+
},
|
|
3877
|
+
with_user: {
|
|
3878
|
+
type: "string",
|
|
3879
|
+
description: "Optional. Return only the DM(s) with this person, as a Slack user id or a name/handle. Use this to find and read the DM with a specific person. Works even for a DM that isn't currently open -- it will open/resume the conversation to read it."
|
|
3880
|
+
}
|
|
3881
|
+
},
|
|
3882
|
+
required: []
|
|
3883
|
+
}
|
|
3884
|
+
},
|
|
3885
|
+
executor: async (args) => listSlackDms(callProxy, {
|
|
3886
|
+
limit: args.limit,
|
|
3887
|
+
with_user: typeof args.with_user === "string" ? args.with_user : void 0
|
|
3535
3888
|
})
|
|
3536
3889
|
};
|
|
3537
3890
|
}
|
|
@@ -3560,7 +3913,7 @@ function createSlackGetChannelHistoryTool(callProxy) {
|
|
|
3560
3913
|
type: "function",
|
|
3561
3914
|
function: {
|
|
3562
3915
|
name: "slack_get_channel_history",
|
|
3563
|
-
description: "Fetch recent messages from a single Slack channel by id. Best-effort and heavily rate-limited (~1 request per minute for the app), so use it sparingly for a specific channel.",
|
|
3916
|
+
description: "Fetch recent messages from a single Slack channel by id. Best-effort and heavily rate-limited (~1 request per minute for the app), so use it sparingly for a specific channel. This is the right tool to read the most recent messages (including just the latest one with limit: 1) from a known channel or DM id, and should be PREFERRED over slack_search_messages whenever the conversation to read is already known or identified.",
|
|
3564
3917
|
parameters: {
|
|
3565
3918
|
type: "object",
|
|
3566
3919
|
properties: {
|
|
@@ -3650,6 +4003,7 @@ function createSlackTools(callProxy) {
|
|
|
3650
4003
|
return {
|
|
3651
4004
|
slack_get_me: createSlackGetMeTool(callProxy),
|
|
3652
4005
|
slack_list_channels: createSlackListChannelsTool(callProxy),
|
|
4006
|
+
slack_list_dms: createSlackListDmsTool(callProxy),
|
|
3653
4007
|
slack_search_messages: createSlackSearchMessagesTool(callProxy),
|
|
3654
4008
|
slack_list_users: createSlackListUsersTool(callProxy),
|
|
3655
4009
|
slack_get_channel_history: createSlackGetChannelHistoryTool(callProxy),
|