@pipedream/slack_v2 0.6.0 → 0.7.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/actions/add-emoji-reaction/add-emoji-reaction.mjs +1 -1
- package/actions/add-reaction/add-reaction.mjs +1 -1
- package/actions/archive-channel/archive-channel.mjs +1 -1
- package/actions/browse-files/browse-files.mjs +1 -1
- package/actions/create-channel/create-channel.mjs +1 -1
- package/actions/create-reminder/create-reminder.mjs +1 -1
- package/actions/delete-file/delete-file.mjs +1 -1
- package/actions/delete-message/delete-message.mjs +48 -6
- package/actions/edit-message/edit-message.mjs +1 -1
- package/actions/find-message/find-message.mjs +1 -1
- package/actions/find-user-by-email/find-user-by-email.mjs +1 -1
- package/actions/find-user-by-id/find-user-by-id.mjs +1 -1
- package/actions/get-channel-details/get-channel-details.mjs +6 -3
- package/actions/get-channel-history/get-channel-history.mjs +15 -2
- package/actions/get-current-user/get-current-user.mjs +2 -2
- package/actions/get-file/get-file.mjs +1 -1
- package/actions/get-thread-replies/get-thread-replies.mjs +17 -2
- package/actions/get-user-details/get-user-details.mjs +8 -4
- package/actions/invite-user-to-channel/invite-user-to-channel.mjs +72 -14
- package/actions/kick-user/kick-user.mjs +1 -1
- package/actions/list-channels/list-channels.mjs +54 -6
- package/actions/list-emojis/list-emojis.mjs +1 -1
- package/actions/list-files/list-files.mjs +1 -1
- package/actions/list-group-members/list-group-members.mjs +1 -1
- package/actions/list-icon-emoji-options/list-icon-emoji-options.mjs +1 -1
- package/actions/list-members-in-channel/list-members-in-channel.mjs +9 -5
- package/actions/list-messages/list-messages.mjs +1 -1
- package/actions/list-reminder-options/list-reminder-options.mjs +1 -1
- package/actions/list-replies/list-replies.mjs +1 -1
- package/actions/list-user-group-options/list-user-group-options.mjs +1 -1
- package/actions/list-users/list-users.mjs +1 -1
- package/actions/post-message/post-message.mjs +1 -1
- package/actions/reply-to-a-message/reply-to-a-message.mjs +1 -1
- package/actions/search/search.mjs +1 -1
- package/actions/send-block-kit-message/send-block-kit-message.mjs +1 -1
- package/actions/send-large-message/send-large-message.mjs +1 -1
- package/actions/send-message/send-message.mjs +1 -1
- package/actions/send-message-advanced/send-message-advanced.mjs +1 -1
- package/actions/send-message-to-channel/send-message-to-channel.mjs +1 -1
- package/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +1 -1
- package/actions/set-channel-description/set-channel-description.mjs +1 -1
- package/actions/set-channel-topic/set-channel-topic.mjs +7 -4
- package/actions/set-status/set-status.mjs +1 -1
- package/actions/update-group-members/update-group-members.mjs +1 -1
- package/actions/update-message/update-message.mjs +1 -1
- package/actions/update-profile/update-profile.mjs +1 -1
- package/actions/upload-file/upload-file.mjs +1 -1
- package/actions/verify-slack-signature/verify-slack-signature.mjs +1 -1
- package/common/constants.mjs +5 -0
- package/common/utils.mjs +69 -0
- package/package.json +1 -1
- package/slack_v2.app.mjs +165 -24
- package/sources/new-channel-created/new-channel-created.mjs +1 -1
- package/sources/new-interaction-event-received/new-interaction-event-received.mjs +1 -1
- package/sources/new-keyword-mention/new-keyword-mention.mjs +1 -1
- package/sources/new-message-in-channels/new-message-in-channels.mjs +1 -1
- package/sources/new-private-channel-created/new-private-channel-created.mjs +1 -1
- package/sources/new-reaction-added/new-reaction-added.mjs +1 -1
- package/sources/new-saved-message/new-saved-message.mjs +1 -1
- package/sources/new-user-added/new-user-added.mjs +1 -1
- package/sources/new-user-mention/new-user-mention.mjs +1 -1
- package/actions/approve-workflow/approve-workflow.mjs +0 -92
|
@@ -3,8 +3,8 @@ import slack from "../../slack_v2.app.mjs";
|
|
|
3
3
|
export default {
|
|
4
4
|
key: "slack_v2-list-members-in-channel",
|
|
5
5
|
name: "List Members in Channel",
|
|
6
|
-
description: "Retrieve members of a channel. [See the documentation](https://api.slack.com/methods/conversations.members)",
|
|
7
|
-
version: "0.0
|
|
6
|
+
description: "Retrieve members of a channel. Accepts a channel ID or NAME (e.g. general or #general) — names are resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.members)",
|
|
7
|
+
version: "0.1.0",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: false,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -22,7 +22,7 @@ export default {
|
|
|
22
22
|
returnUsernames: {
|
|
23
23
|
type: "boolean",
|
|
24
24
|
label: "Return Usernames",
|
|
25
|
-
description: "Optionally, return usernames in addition to IDs",
|
|
25
|
+
description: "Optionally, return usernames in addition to IDs. The username may be `null` for members whose name cannot be resolved, such as external (Slack Connect) users.",
|
|
26
26
|
optional: true,
|
|
27
27
|
},
|
|
28
28
|
pageSize: {
|
|
@@ -40,8 +40,12 @@ export default {
|
|
|
40
40
|
},
|
|
41
41
|
async run({ $ }) {
|
|
42
42
|
let channelMembers = [];
|
|
43
|
+
// Accept a channel NAME as well as an ID. Every AI-optimized tool in this app resolves
|
|
44
|
+
// names server-side, so an agent that read "#seinfeld-general" from the prompt
|
|
45
|
+
// reasonably passes it here too — and used to get channel_not_found.
|
|
46
|
+
const channel = await this.slack.resolveChannelId(this.conversation);
|
|
43
47
|
const params = {
|
|
44
|
-
channel
|
|
48
|
+
channel,
|
|
45
49
|
limit: this.pageSize,
|
|
46
50
|
};
|
|
47
51
|
let page = 0;
|
|
@@ -59,7 +63,7 @@ export default {
|
|
|
59
63
|
const usernames = await this.slack.userNameLookup(channelMembers);
|
|
60
64
|
channelMembers = channelMembers?.map((id) => ({
|
|
61
65
|
id,
|
|
62
|
-
username: usernames[id],
|
|
66
|
+
username: usernames[id] ?? null,
|
|
63
67
|
})) || [];
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
name: "List Messages",
|
|
6
6
|
description:
|
|
7
7
|
"Retrieve messages from a Slack conversation, including reactions. [See the documentation](https://api.slack.com/methods/conversations.history)",
|
|
8
|
-
version: "0.0.
|
|
8
|
+
version: "0.0.5",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: false,
|
|
11
11
|
openWorldHint: true,
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
key: "slack_v2-list-replies",
|
|
6
6
|
name: "List Replies",
|
|
7
7
|
description: "Retrieve a thread of messages posted to a conversation. [See the documentation](https://api.slack.com/methods/conversations.replies)",
|
|
8
|
-
version: "0.0.
|
|
8
|
+
version: "0.0.30",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: false,
|
|
11
11
|
openWorldHint: true,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-list-user-group-options",
|
|
5
5
|
name: "List User Group Options",
|
|
6
6
|
description: "Retrieves available options for the User Group field.",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.2",
|
|
8
8
|
type: "action",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: false,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-list-users",
|
|
5
5
|
name: "List Users",
|
|
6
6
|
description: "Return a list of all users in a workspace. [See the documentation](https://api.slack.com/methods/users.list)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.29",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: false,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -10,7 +10,7 @@ export default {
|
|
|
10
10
|
+ " To reply to a thread, provide `thread_ts` from **Get Channel History**."
|
|
11
11
|
+ " Supports plain text with Slack mrkdwn formatting and Block Kit blocks."
|
|
12
12
|
+ " [See the documentation](https://api.slack.com/methods/chat.postMessage)",
|
|
13
|
-
version: "0.0.
|
|
13
|
+
version: "0.0.2",
|
|
14
14
|
type: "action",
|
|
15
15
|
annotations: {
|
|
16
16
|
destructiveHint: false,
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
key: "slack_v2-reply-to-a-message",
|
|
7
7
|
name: "Reply to a Message Thread",
|
|
8
8
|
description: "Send a message as a threaded reply. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here",
|
|
9
|
-
version: "0.2.
|
|
9
|
+
version: "0.2.5",
|
|
10
10
|
annotations: {
|
|
11
11
|
destructiveHint: false,
|
|
12
12
|
openWorldHint: true,
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
+ " Use **Get User Details** first to find your user ID for filtering by 'my' messages."
|
|
10
10
|
+ " Returns matching messages with channel context, timestamps, and permalinks."
|
|
11
11
|
+ " [See the documentation](https://api.slack.com/methods/assistant.search.context)",
|
|
12
|
-
version: "0.0.
|
|
12
|
+
version: "0.0.2",
|
|
13
13
|
type: "action",
|
|
14
14
|
annotations: {
|
|
15
15
|
destructiveHint: false,
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
key: "slack_v2-send-block-kit-message",
|
|
8
8
|
name: "Build and Send a Block Kit Message",
|
|
9
9
|
description: "Configure custom blocks and send to a channel, group, or user. [See the documentation](https://api.slack.com/tools/block-kit-builder).",
|
|
10
|
-
version: "0.5.
|
|
10
|
+
version: "0.5.5",
|
|
11
11
|
annotations: {
|
|
12
12
|
destructiveHint: false,
|
|
13
13
|
openWorldHint: true,
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
key: "slack_v2-send-large-message",
|
|
6
6
|
name: "Send a Large Message (3000+ characters)",
|
|
7
7
|
description: "Send a large message (more than 3000 characters) to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here",
|
|
8
|
-
version: "0.1.
|
|
8
|
+
version: "0.1.5",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: false,
|
|
11
11
|
openWorldHint: true,
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
key: "slack_v2-send-message",
|
|
7
7
|
name: "Send Message",
|
|
8
8
|
description: "Send a message to a user, group, private channel or public channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)",
|
|
9
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.5",
|
|
10
10
|
annotations: {
|
|
11
11
|
destructiveHint: false,
|
|
12
12
|
openWorldHint: true,
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
key: "slack_v2-send-message-advanced",
|
|
8
8
|
name: "Send Message (Advanced)",
|
|
9
9
|
description: "Customize advanced settings and send a message to a channel, group or user. See [postMessage](https://api.slack.com/methods/chat.postMessage) or [scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) docs here",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.5",
|
|
11
11
|
annotations: {
|
|
12
12
|
destructiveHint: false,
|
|
13
13
|
openWorldHint: true,
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
key: "slack_v2-send-message-to-channel",
|
|
7
7
|
name: "Send Message to Channel",
|
|
8
8
|
description: "Send a message to a public or private channel. [See the documentation](https://api.slack.com/methods/chat.postMessage)",
|
|
9
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.5",
|
|
10
10
|
annotations: {
|
|
11
11
|
destructiveHint: false,
|
|
12
12
|
openWorldHint: true,
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
key: "slack_v2-send-message-to-user-or-group",
|
|
8
8
|
name: "Send Message to User or Group",
|
|
9
9
|
description: "Send a message to a user or group. [See the documentation](https://api.slack.com/methods/chat.postMessage)",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.5",
|
|
11
11
|
annotations: {
|
|
12
12
|
destructiveHint: false,
|
|
13
13
|
openWorldHint: true,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-set-channel-description",
|
|
5
5
|
name: "Set Channel Description",
|
|
6
6
|
description: "Change the description or purpose of a channel. [See the documentation](https://api.slack.com/methods/conversations.setPurpose)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.15",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: false,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -3,8 +3,8 @@ import slack from "../../slack_v2.app.mjs";
|
|
|
3
3
|
export default {
|
|
4
4
|
key: "slack_v2-set-channel-topic",
|
|
5
5
|
name: "Set Channel Topic",
|
|
6
|
-
description: "Set the topic on a
|
|
7
|
-
version: "0.0
|
|
6
|
+
description: "Set the topic on a channel, specified by ID or by name — names are resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.setTopic)",
|
|
7
|
+
version: "0.1.0",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: true,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -27,11 +27,14 @@ export default {
|
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
29
|
async run({ $ }) {
|
|
30
|
+
// Accept a channel NAME as well as an ID. Slack answers a name here with a bare
|
|
31
|
+
// `internal_error`, which tells an agent nothing about what it did wrong.
|
|
32
|
+
const channel = await this.slack.resolveChannelId(this.conversation);
|
|
30
33
|
const response = await this.slack.setChannelTopic({
|
|
31
|
-
channel
|
|
34
|
+
channel,
|
|
32
35
|
topic: this.topic,
|
|
33
36
|
});
|
|
34
|
-
$.export("$summary", `Successfully set topic for channel with ID ${
|
|
37
|
+
$.export("$summary", `Successfully set topic for channel with ID ${channel}`);
|
|
35
38
|
return response;
|
|
36
39
|
},
|
|
37
40
|
};
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-set-status",
|
|
5
5
|
name: "Set Status",
|
|
6
6
|
description: "Set the current status for a user. [See the documentation](https://api.slack.com/methods/users.profile.set)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.14",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: true,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-update-group-members",
|
|
5
5
|
name: "Update Group Members",
|
|
6
6
|
description: "Update the list of users for a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.update)",
|
|
7
|
-
version: "0.0.
|
|
7
|
+
version: "0.0.15",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: true,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "slack_v2-update-message",
|
|
5
5
|
name: "Update Message",
|
|
6
6
|
description: "Update a message. [See the documentation](https://api.slack.com/methods/chat.update)",
|
|
7
|
-
version: "0.2.
|
|
7
|
+
version: "0.2.5",
|
|
8
8
|
annotations: {
|
|
9
9
|
destructiveHint: true,
|
|
10
10
|
openWorldHint: true,
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
key: "slack_v2-update-profile",
|
|
6
6
|
name: "Update Profile",
|
|
7
7
|
description: "Update basic profile field such as name or title. [See the documentation](https://api.slack.com/methods/users.profile.set)",
|
|
8
|
-
version: "0.0.
|
|
8
|
+
version: "0.0.30",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: true,
|
|
11
11
|
openWorldHint: true,
|
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
key: "slack_v2-upload-file",
|
|
9
9
|
name: "Upload File",
|
|
10
10
|
description: "Upload a file. [See the documentation](https://api.slack.com/messaging/files#uploading_files)",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.9",
|
|
12
12
|
annotations: {
|
|
13
13
|
destructiveHint: false,
|
|
14
14
|
openWorldHint: true,
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
key: "slack_v2-verify-slack-signature",
|
|
6
6
|
name: "Verify Slack Signature",
|
|
7
7
|
description: "Verifying requests from Slack, slack signs its requests using a secret that's unique to your app. [See the documentation](https://api.slack.com/authentication/verifying-requests-from-slack)",
|
|
8
|
-
version: "0.0.
|
|
8
|
+
version: "0.0.4",
|
|
9
9
|
annotations: {
|
|
10
10
|
destructiveHint: false,
|
|
11
11
|
openWorldHint: true,
|
package/common/constants.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const MAX_RESOURCES = 800;
|
|
2
2
|
const LIMIT = 250;
|
|
3
|
+
// Caps `users.list` pagination in name lookups, since unresolvable ids (e.g.
|
|
4
|
+
// external Slack Connect users) would otherwise force a full directory scan
|
|
5
|
+
// and exhaust the method's rate limit.
|
|
6
|
+
const MAX_NAME_LOOKUP_PAGES = 5;
|
|
3
7
|
|
|
4
8
|
const CHANNEL_TYPE = {
|
|
5
9
|
PUBLIC: "public_channel",
|
|
@@ -26,6 +30,7 @@ const CHANNEL_TYPE_OPTIONS = [
|
|
|
26
30
|
export default {
|
|
27
31
|
MAX_RESOURCES,
|
|
28
32
|
LIMIT,
|
|
33
|
+
MAX_NAME_LOOKUP_PAGES,
|
|
29
34
|
CHANNEL_TYPE,
|
|
30
35
|
CHANNEL_TYPE_OPTIONS,
|
|
31
36
|
};
|
package/common/utils.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the `fields` projection offered by the list/history actions.
|
|
3
|
+
*
|
|
4
|
+
* Three actions (Get Channel History, Get Thread Replies, List Channels) let the caller
|
|
5
|
+
* name the properties they want back. Both halves of that feature — normalizing the prop
|
|
6
|
+
* value and applying it — were copy-pasted into each action; keeping one copy means a fix
|
|
7
|
+
* to either lands everywhere at once.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a `string[]` prop that an LLM caller may well send as a CSV string.
|
|
12
|
+
*
|
|
13
|
+
* `fields` is declared `string[]`, but MCP/LLM callers routinely pass `"text,ts,user"`.
|
|
14
|
+
* Spreading a bare string into a keep-set yields single characters, which match no key,
|
|
15
|
+
* so the caller would silently get back nothing at all.
|
|
16
|
+
*
|
|
17
|
+
* @param {string|string[]|undefined} value - the raw prop value
|
|
18
|
+
* @returns {string[]|null} the requested field names, or null when none were requested
|
|
19
|
+
*/
|
|
20
|
+
export function parseFields(value) {
|
|
21
|
+
if (Array.isArray(value)) return value;
|
|
22
|
+
if (typeof value === "string" && value.length) {
|
|
23
|
+
return value.split(",")
|
|
24
|
+
.map((f) => f.trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Keep only the requested properties of a record.
|
|
32
|
+
*
|
|
33
|
+
* Unknown names are ignored rather than returned as `undefined`, so a typo shrinks the
|
|
34
|
+
* payload instead of corrupting it with null-valued keys.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} record - a message or channel object
|
|
37
|
+
* @param {string[]} fields - property names to keep
|
|
38
|
+
* @returns {object} the projected record
|
|
39
|
+
*/
|
|
40
|
+
export function pickFields(record, fields) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const field of fields) {
|
|
43
|
+
if (record[field] !== undefined) out[field] = record[field];
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Apply a `fields` projection to a list, or return it untouched when none was requested.
|
|
50
|
+
*
|
|
51
|
+
* The no-fields path returns the ORIGINAL array, which is what makes `fields` additive:
|
|
52
|
+
* a caller that omits it gets exactly what these actions have always returned.
|
|
53
|
+
*
|
|
54
|
+
* @param {object[]} records - the records to project
|
|
55
|
+
* @param {string|string[]|undefined} value - the raw `fields` prop value
|
|
56
|
+
* @returns {object[]} projected records, or `records` unchanged
|
|
57
|
+
*/
|
|
58
|
+
export function projectFields(records, value) {
|
|
59
|
+
const fields = parseFields(value);
|
|
60
|
+
return fields?.length
|
|
61
|
+
? records.map((r) => pickFields(r, fields))
|
|
62
|
+
: records;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
parseFields,
|
|
67
|
+
pickFields,
|
|
68
|
+
projectFields,
|
|
69
|
+
};
|
package/package.json
CHANGED
package/slack_v2.app.mjs
CHANGED
|
@@ -11,32 +11,82 @@ export default {
|
|
|
11
11
|
user: {
|
|
12
12
|
type: "string",
|
|
13
13
|
label: "User",
|
|
14
|
-
description: "Select a user",
|
|
14
|
+
description: "Select a user, or search by name or email",
|
|
15
|
+
useQuery: true,
|
|
15
16
|
async options({
|
|
16
|
-
prevContext, channelId,
|
|
17
|
+
prevContext, channelId, query,
|
|
17
18
|
}) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
let nameQuery = query
|
|
20
|
+
?.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/^@/, "");
|
|
23
|
+
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(nameQuery ?? "")) {
|
|
24
|
+
try {
|
|
25
|
+
const { user } = await this.lookupUserByEmail({
|
|
26
|
+
email: nameQuery,
|
|
27
|
+
throwRateLimitError: true,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
options: [
|
|
31
|
+
{
|
|
32
|
+
label: user.profile?.real_name || user.name,
|
|
33
|
+
value: user.id,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
// Token may lack `users:read.email`, or no member has this email.
|
|
39
|
+
// Fall back to matching names below with the email's local part.
|
|
40
|
+
nameQuery = nameQuery.split("@")[0].replace(/[._-]+/g, " ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let channelMembers;
|
|
23
44
|
if (channelId) {
|
|
24
45
|
const { members } = await this.listChannelMembers({
|
|
25
46
|
channel: channelId,
|
|
26
47
|
throwRateLimitError: true,
|
|
27
48
|
});
|
|
28
|
-
|
|
29
|
-
.filter((c) => members.includes(c.user || c.id));
|
|
49
|
+
channelMembers = new Set(members);
|
|
30
50
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
51
|
+
const options = [];
|
|
52
|
+
let cursor = prevContext?.cursor;
|
|
53
|
+
let pages = 0;
|
|
54
|
+
do {
|
|
55
|
+
const {
|
|
56
|
+
members,
|
|
57
|
+
response_metadata: { next_cursor: nextCursor },
|
|
58
|
+
} = await this.usersList({
|
|
59
|
+
limit: constants.LIMIT,
|
|
60
|
+
cursor,
|
|
61
|
+
throwRateLimitError: true,
|
|
62
|
+
});
|
|
63
|
+
for (const member of members) {
|
|
64
|
+
const isSelectable = !member.deleted
|
|
65
|
+
&& !member.is_bot
|
|
66
|
+
&& member.id !== "USLACKBOT"
|
|
67
|
+
&& (!channelMembers || channelMembers.has(member.id));
|
|
68
|
+
if (isSelectable) {
|
|
69
|
+
const names = [
|
|
70
|
+
member.profile?.real_name,
|
|
71
|
+
member.profile?.display_name,
|
|
72
|
+
member.name,
|
|
73
|
+
];
|
|
74
|
+
if (!nameQuery || names.some((name) => name?.toLowerCase().includes(nameQuery))) {
|
|
75
|
+
options.push({
|
|
76
|
+
label: names.find(Boolean) ?? member.id,
|
|
77
|
+
value: member.id,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
cursor = nextCursor;
|
|
83
|
+
} while (cursor
|
|
84
|
+
&& options.length < constants.LIMIT
|
|
85
|
+
&& ++pages < constants.MAX_NAME_LOOKUP_PAGES);
|
|
33
86
|
return {
|
|
34
|
-
options
|
|
35
|
-
label: `${realNames[c.user]}`,
|
|
36
|
-
value: c.user || c.id,
|
|
37
|
-
})),
|
|
87
|
+
options,
|
|
38
88
|
context: {
|
|
39
|
-
cursor
|
|
89
|
+
cursor,
|
|
40
90
|
},
|
|
41
91
|
};
|
|
42
92
|
},
|
|
@@ -146,12 +196,12 @@ export default {
|
|
|
146
196
|
options: conversations.map((c) => {
|
|
147
197
|
if (c.is_im) {
|
|
148
198
|
return {
|
|
149
|
-
label: `Direct messaging with: ${realNames[c.user]}`,
|
|
199
|
+
label: `Direct messaging with: ${realNames?.[c.user] ?? c.user}`,
|
|
150
200
|
value: c.id,
|
|
151
201
|
};
|
|
152
202
|
} else if (c.is_mpim) {
|
|
153
203
|
const usernames = c.purpose.value.match(/@[\w.-]+/g) || [];
|
|
154
|
-
const realnames = usernames.map((u) => realNames[u.slice(1)] || u);
|
|
204
|
+
const realnames = usernames.map((u) => realNames?.[u.slice(1)] || u);
|
|
155
205
|
return {
|
|
156
206
|
label: realnames.length
|
|
157
207
|
? `Group messaging with: ${realnames.join(", ")}`
|
|
@@ -205,8 +255,11 @@ export default {
|
|
|
205
255
|
.filter(channelsFilter)
|
|
206
256
|
.map((c) => {
|
|
207
257
|
if (c.is_im) {
|
|
258
|
+
const userName = userNames?.[c.user];
|
|
208
259
|
return {
|
|
209
|
-
label: `Direct messaging with:
|
|
260
|
+
label: `Direct messaging with: ${userName
|
|
261
|
+
? `@${userName}`
|
|
262
|
+
: c.user}`,
|
|
210
263
|
value: c.id,
|
|
211
264
|
};
|
|
212
265
|
} else if (c.is_mpim) {
|
|
@@ -626,8 +679,13 @@ export default {
|
|
|
626
679
|
conversations,
|
|
627
680
|
};
|
|
628
681
|
},
|
|
682
|
+
// Resolves user names by scanning `users.list`, capped at
|
|
683
|
+
// MAX_NAME_LOOKUP_PAGES pages per call: some ids may be unresolvable
|
|
684
|
+
// (e.g. external Slack Connect users), so callers must handle missing
|
|
685
|
+
// entries in the result.
|
|
629
686
|
async userNameLookup(ids = [], throwRateLimitError = true, args = {}) {
|
|
630
687
|
let cursor;
|
|
688
|
+
let pages = 0;
|
|
631
689
|
const userNames = {};
|
|
632
690
|
do {
|
|
633
691
|
const {
|
|
@@ -647,13 +705,21 @@ export default {
|
|
|
647
705
|
}
|
|
648
706
|
|
|
649
707
|
cursor = nextCursor;
|
|
650
|
-
} while (cursor
|
|
708
|
+
} while (cursor
|
|
709
|
+
&& ++pages < constants.MAX_NAME_LOOKUP_PAGES
|
|
710
|
+
&& Object.keys(userNames).length < ids.length);
|
|
651
711
|
return userNames;
|
|
652
712
|
},
|
|
713
|
+
// Resolves real names by scanning `users.list`, capped at
|
|
714
|
+
// MAX_NAME_LOOKUP_PAGES pages per call: some ids/usernames may be
|
|
715
|
+
// unresolvable (e.g. external Slack Connect users, or stale usernames in
|
|
716
|
+
// mpim purpose text), so callers must handle missing entries in the
|
|
717
|
+
// result.
|
|
653
718
|
async realNameLookup(ids = [], usernames = [], throwRateLimitError = true, args = {}) {
|
|
654
719
|
const idSet = new Set(ids);
|
|
655
720
|
const usernameSet = new Set(usernames);
|
|
656
721
|
let cursor;
|
|
722
|
+
let pages = 0;
|
|
657
723
|
const realNames = {};
|
|
658
724
|
const targetCount = ids.length + usernames.length;
|
|
659
725
|
do {
|
|
@@ -677,7 +743,9 @@ export default {
|
|
|
677
743
|
}
|
|
678
744
|
|
|
679
745
|
cursor = nextCursor;
|
|
680
|
-
} while (cursor
|
|
746
|
+
} while (cursor
|
|
747
|
+
&& ++pages < constants.MAX_NAME_LOOKUP_PAGES
|
|
748
|
+
&& Object.keys(realNames).length < targetCount);
|
|
681
749
|
return realNames;
|
|
682
750
|
},
|
|
683
751
|
async maybeAddAppToChannels(channelIds = []) {
|
|
@@ -899,10 +967,15 @@ export default {
|
|
|
899
967
|
assistantSearch(args = {}) {
|
|
900
968
|
args.count ||= constants.LIMIT;
|
|
901
969
|
// Uses apiCall directly since assistant.search.context is not exposed as
|
|
902
|
-
// a method on WebClient
|
|
903
|
-
|
|
970
|
+
// a method on WebClient — but it must STILL go through _withRetries. Calling
|
|
971
|
+
// apiCall bare was the one path in this app that skipped the retry wrapper, and
|
|
972
|
+
// the client is built with `rejectRateLimitedCalls: true`, so a 429 rejected
|
|
973
|
+
// instantly instead of backing off. assistant.search.context rate-limits readily
|
|
974
|
+
// (Slack returns retryAfter: 60), which surfaced to agents as a hard tool error
|
|
975
|
+
// on 8 of 12 search calls in one eval run.
|
|
976
|
+
return this._withRetries(() => this.sdk().apiCall("assistant.search.context", {
|
|
904
977
|
...args,
|
|
905
|
-
});
|
|
978
|
+
}));
|
|
906
979
|
},
|
|
907
980
|
/**
|
|
908
981
|
* Lists reactions made by a user.
|
|
@@ -1124,6 +1197,74 @@ export default {
|
|
|
1124
1197
|
normalizeChannel(input) {
|
|
1125
1198
|
return input.replace(/^#/, "");
|
|
1126
1199
|
},
|
|
1200
|
+
/**
|
|
1201
|
+
* Accept a user ID, an email, or a display/real name and return the user ID.
|
|
1202
|
+
* Agents naturally pass whatever identifier the user said — an email in
|
|
1203
|
+
* "invite dylan@pipedream.com" — and the raw API answers `user_not_found`,
|
|
1204
|
+
* which reads as a broken tool rather than a wrong-shaped argument.
|
|
1205
|
+
*/
|
|
1206
|
+
async resolveUserId(input) {
|
|
1207
|
+
if (/^[UW][A-Z0-9]{6,}$/i.test(input)) return input;
|
|
1208
|
+
if (/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(input)) {
|
|
1209
|
+
const { user } = await this.makeRequest({
|
|
1210
|
+
method: "users.lookupByEmail",
|
|
1211
|
+
email: input,
|
|
1212
|
+
});
|
|
1213
|
+
return user.id;
|
|
1214
|
+
}
|
|
1215
|
+
const name = input.replace(/^@/, "").toLowerCase();
|
|
1216
|
+
const matches = [];
|
|
1217
|
+
let cursor;
|
|
1218
|
+
do {
|
|
1219
|
+
const {
|
|
1220
|
+
members, response_metadata: metadata,
|
|
1221
|
+
} = await this.makeRequest({
|
|
1222
|
+
method: "users.list",
|
|
1223
|
+
limit: 200,
|
|
1224
|
+
cursor,
|
|
1225
|
+
});
|
|
1226
|
+
for (const member of members) {
|
|
1227
|
+
const isMatch = [
|
|
1228
|
+
member.name,
|
|
1229
|
+
member.real_name,
|
|
1230
|
+
member.profile?.display_name,
|
|
1231
|
+
].filter(Boolean).some((n) => String(n).toLowerCase() === name);
|
|
1232
|
+
if (isMatch) matches.push(member);
|
|
1233
|
+
}
|
|
1234
|
+
cursor = metadata?.next_cursor;
|
|
1235
|
+
} while (cursor);
|
|
1236
|
+
if (matches.length === 1) return matches[0].id;
|
|
1237
|
+
if (matches.length > 1) {
|
|
1238
|
+
throw new ConfigurationError(`Multiple users match "${input}". Provide a user ID or an email address instead.`);
|
|
1239
|
+
}
|
|
1240
|
+
throw new ConfigurationError(`User "${input}" not found. Provide a user ID, an email address, or an exact display name.`);
|
|
1241
|
+
},
|
|
1242
|
+
/**
|
|
1243
|
+
* Resolve one OR MORE user identifiers to the comma-separated `users` string Slack's
|
|
1244
|
+
* conversations.* methods accept.
|
|
1245
|
+
*
|
|
1246
|
+
* The list form has to keep working: `users` is documented as comma-separated, the prop
|
|
1247
|
+
* is a plain string, and callers that predate resolveUserId() pass "U123,U456" straight
|
|
1248
|
+
* through. Resolving the whole string as a single identifier would send it to an
|
|
1249
|
+
* exhaustive users.list scan that cannot match, then throw.
|
|
1250
|
+
*
|
|
1251
|
+
* @param {string|string[]} input - id / email / display name, or a comma-separated list
|
|
1252
|
+
* or array of them
|
|
1253
|
+
* @returns {Promise<string>} Comma-separated user IDs
|
|
1254
|
+
*/
|
|
1255
|
+
async resolveUserIds(input) {
|
|
1256
|
+
const raw = Array.isArray(input)
|
|
1257
|
+
? input
|
|
1258
|
+
: String(input).split(",");
|
|
1259
|
+
const tokens = raw
|
|
1260
|
+
.map((t) => String(t).trim())
|
|
1261
|
+
.filter(Boolean);
|
|
1262
|
+
const ids = [];
|
|
1263
|
+
for (const token of tokens) {
|
|
1264
|
+
ids.push(await this.resolveUserId(token));
|
|
1265
|
+
}
|
|
1266
|
+
return ids.join(",");
|
|
1267
|
+
},
|
|
1127
1268
|
/**
|
|
1128
1269
|
* Resolves a channel name (e.g. "general", "#general") to its ID.
|
|
1129
1270
|
* If the input already looks like an ID (starts with C/D/G/U + 8+ alphanums),
|