@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.
Files changed (62) hide show
  1. package/actions/add-emoji-reaction/add-emoji-reaction.mjs +1 -1
  2. package/actions/add-reaction/add-reaction.mjs +1 -1
  3. package/actions/archive-channel/archive-channel.mjs +1 -1
  4. package/actions/browse-files/browse-files.mjs +1 -1
  5. package/actions/create-channel/create-channel.mjs +1 -1
  6. package/actions/create-reminder/create-reminder.mjs +1 -1
  7. package/actions/delete-file/delete-file.mjs +1 -1
  8. package/actions/delete-message/delete-message.mjs +48 -6
  9. package/actions/edit-message/edit-message.mjs +1 -1
  10. package/actions/find-message/find-message.mjs +1 -1
  11. package/actions/find-user-by-email/find-user-by-email.mjs +1 -1
  12. package/actions/find-user-by-id/find-user-by-id.mjs +1 -1
  13. package/actions/get-channel-details/get-channel-details.mjs +6 -3
  14. package/actions/get-channel-history/get-channel-history.mjs +15 -2
  15. package/actions/get-current-user/get-current-user.mjs +2 -2
  16. package/actions/get-file/get-file.mjs +1 -1
  17. package/actions/get-thread-replies/get-thread-replies.mjs +17 -2
  18. package/actions/get-user-details/get-user-details.mjs +8 -4
  19. package/actions/invite-user-to-channel/invite-user-to-channel.mjs +72 -14
  20. package/actions/kick-user/kick-user.mjs +1 -1
  21. package/actions/list-channels/list-channels.mjs +54 -6
  22. package/actions/list-emojis/list-emojis.mjs +1 -1
  23. package/actions/list-files/list-files.mjs +1 -1
  24. package/actions/list-group-members/list-group-members.mjs +1 -1
  25. package/actions/list-icon-emoji-options/list-icon-emoji-options.mjs +1 -1
  26. package/actions/list-members-in-channel/list-members-in-channel.mjs +9 -5
  27. package/actions/list-messages/list-messages.mjs +1 -1
  28. package/actions/list-reminder-options/list-reminder-options.mjs +1 -1
  29. package/actions/list-replies/list-replies.mjs +1 -1
  30. package/actions/list-user-group-options/list-user-group-options.mjs +1 -1
  31. package/actions/list-users/list-users.mjs +1 -1
  32. package/actions/post-message/post-message.mjs +1 -1
  33. package/actions/reply-to-a-message/reply-to-a-message.mjs +1 -1
  34. package/actions/search/search.mjs +1 -1
  35. package/actions/send-block-kit-message/send-block-kit-message.mjs +1 -1
  36. package/actions/send-large-message/send-large-message.mjs +1 -1
  37. package/actions/send-message/send-message.mjs +1 -1
  38. package/actions/send-message-advanced/send-message-advanced.mjs +1 -1
  39. package/actions/send-message-to-channel/send-message-to-channel.mjs +1 -1
  40. package/actions/send-message-to-user-or-group/send-message-to-user-or-group.mjs +1 -1
  41. package/actions/set-channel-description/set-channel-description.mjs +1 -1
  42. package/actions/set-channel-topic/set-channel-topic.mjs +7 -4
  43. package/actions/set-status/set-status.mjs +1 -1
  44. package/actions/update-group-members/update-group-members.mjs +1 -1
  45. package/actions/update-message/update-message.mjs +1 -1
  46. package/actions/update-profile/update-profile.mjs +1 -1
  47. package/actions/upload-file/upload-file.mjs +1 -1
  48. package/actions/verify-slack-signature/verify-slack-signature.mjs +1 -1
  49. package/common/constants.mjs +5 -0
  50. package/common/utils.mjs +69 -0
  51. package/package.json +1 -1
  52. package/slack_v2.app.mjs +165 -24
  53. package/sources/new-channel-created/new-channel-created.mjs +1 -1
  54. package/sources/new-interaction-event-received/new-interaction-event-received.mjs +1 -1
  55. package/sources/new-keyword-mention/new-keyword-mention.mjs +1 -1
  56. package/sources/new-message-in-channels/new-message-in-channels.mjs +1 -1
  57. package/sources/new-private-channel-created/new-private-channel-created.mjs +1 -1
  58. package/sources/new-reaction-added/new-reaction-added.mjs +1 -1
  59. package/sources/new-saved-message/new-saved-message.mjs +1 -1
  60. package/sources/new-user-added/new-user-added.mjs +1 -1
  61. package/sources/new-user-mention/new-user-mention.mjs +1 -1
  62. package/actions/approve-workflow/approve-workflow.mjs +0 -92
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-add-emoji-reaction",
5
5
  name: "Add Emoji Reaction",
6
6
  description: "Add an emoji reaction to a message. [See the documentation](https://api.slack.com/methods/reactions.add)",
7
- version: "0.0.20",
7
+ version: "0.0.22",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -9,7 +9,7 @@ export default {
9
9
  + " Use **Get Channel History** or **Search** to find the message timestamp."
10
10
  + " Emoji name should be without colons (e.g. `thumbsup`, `fire`, `heart`)."
11
11
  + " [See the documentation](https://api.slack.com/methods/reactions.add)",
12
- version: "0.0.1",
12
+ version: "0.0.2",
13
13
  type: "action",
14
14
  annotations: {
15
15
  destructiveHint: false,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "slack_v2-archive-channel",
6
6
  name: "Archive Channel",
7
7
  description: "Archive a channel. [See the documentation](https://api.slack.com/methods/conversations.archive)",
8
- version: "0.0.28",
8
+ version: "0.0.30",
9
9
  annotations: {
10
10
  destructiveHint: true,
11
11
  openWorldHint: true,
@@ -9,7 +9,7 @@ export default {
9
9
  + " Filter by file type (e.g. `images`, `pdfs`, `snippets`)."
10
10
  + " Returns file metadata including name, type, size, and download URL."
11
11
  + " [See the documentation](https://api.slack.com/methods/files.list)",
12
- version: "0.0.1",
12
+ version: "0.0.2",
13
13
  type: "action",
14
14
  annotations: {
15
15
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-create-channel",
5
5
  name: "Create a Channel",
6
6
  description: "Create a new channel. [See the documentation](https://api.slack.com/methods/conversations.create)",
7
- version: "0.0.29",
7
+ version: "0.0.30",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-create-reminder",
5
5
  name: "Create Reminder",
6
6
  description: "Create a reminder. [See the documentation](https://api.slack.com/methods/reminders.add)",
7
- version: "0.0.29",
7
+ version: "0.0.31",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-delete-file",
5
5
  name: "Delete File",
6
6
  description: "Delete a file. [See the documentation](https://api.slack.com/methods/files.delete)",
7
- version: "0.0.28",
7
+ version: "0.0.30",
8
8
  annotations: {
9
9
  destructiveHint: true,
10
10
  openWorldHint: true,
@@ -3,8 +3,15 @@ import slack from "../../slack_v2.app.mjs";
3
3
  export default {
4
4
  key: "slack_v2-delete-message",
5
5
  name: "Delete Message",
6
- description: "Delete a message. [See the documentation](https://api.slack.com/methods/chat.delete)",
7
- version: "0.1.3",
6
+ description:
7
+ "Permanently delete a message. **This cannot be undone — confirm the exact message with the"
8
+ + " user before calling it.** Identify the message first with **Get Channel History** or"
9
+ + " **Search** and quote its text back, rather than deleting by position ('the last one')."
10
+ + " Accepts a channel ID or NAME for the conversation, resolved automatically. Slack only"
11
+ + " lets an identity delete its own messages, so this deletes as whichever identity posted:"
12
+ + " it retries automatically with the other identity if the first attempt returns"
13
+ + " `cant_delete_message`. [See the documentation](https://api.slack.com/methods/chat.delete)",
14
+ version: "0.2.0",
8
15
  annotations: {
9
16
  destructiveHint: true,
10
17
  openWorldHint: true,
@@ -30,15 +37,50 @@ export default {
30
37
  slack,
31
38
  "as_user",
32
39
  ],
33
- description: "Pass true to update the message as the authed user. Bot users in this context are considered authed users.",
40
+ description: "Pass true to delete the message as the authed user. Bot users in this context are considered authed users. Leave unset — the action falls back to the other identity automatically when Slack refuses.",
34
41
  },
35
42
  },
36
43
  async run({ $ }) {
37
- const response = await this.slack.deleteMessage({
38
- channel: this.conversation,
44
+ // conversation accepts a channel name as well as an ID (like every other AI-optimized
45
+ // action in this app) — resolve it once up front so both delete attempts below reuse
46
+ // the same ID instead of re-resolving (or silently failing on a name) per attempt.
47
+ const channel = await this.slack.resolveChannelId(this.conversation);
48
+
49
+ // Slack refuses `chat.delete` unless the calling identity authored the message, and
50
+ // this app picks the token from `as_user`: false routes to the BOT token, true/unset
51
+ // to the USER token. post-message sends no as_user, so it posts as the USER — meaning
52
+ // the old default here (as_user: false → bot token) could not delete anything
53
+ // post-message had just created. "Post it, then take it back" failed every single
54
+ // time with cant_delete_message. Rather than flip the default (a behaviour change for
55
+ // existing workflows), try the configured identity and fall back to the other one.
56
+ const attempt = (as_user) => this.slack.deleteMessage({
57
+ channel,
39
58
  ts: this.timestamp,
40
- as_user: this.as_user,
59
+ as_user,
41
60
  });
61
+
62
+ // makeRequest() routes to the bot token on `as_user === false` and to the user token for
63
+ // anything else, so the "other identity" is a flip between exactly those two values.
64
+ // Deriving it with `!this.as_user` made the fallback a no-op whenever as_user arrived
65
+ // nullish: undefined and true both route to the user token, so it retried as the same
66
+ // identity that had just been refused. Attempt 1 still passes the configured value
67
+ // verbatim, so a working call is byte-identical to before.
68
+ const other = this.as_user === false;
69
+
70
+ // The fallback only reaches a genuinely different identity when a bot token exists:
71
+ // that's the only case where flipping as_user changes which token makeRequest() picks.
72
+ // Without a bot token both attempts hit the same user token, so retrying just repeats
73
+ // the same cant_delete_message failure.
74
+ const hasBotToken = Boolean(this.slack.getBotToken());
75
+
76
+ let response;
77
+ try {
78
+ response = await attempt(this.as_user);
79
+ } catch (error) {
80
+ if (!hasBotToken || !`${error}`.includes("cant_delete_message")) throw error;
81
+ response = await attempt(other);
82
+ }
83
+
42
84
  $.export("$summary", "Successfully deleted message.");
43
85
  return response;
44
86
  },
@@ -10,7 +10,7 @@ export default {
10
10
  + " Requires the message timestamp (`ts`) from **Get Channel History** or **Post Message**."
11
11
  + " You can only edit messages posted by the same token/user."
12
12
  + " [See the documentation](https://api.slack.com/methods/chat.update)",
13
- version: "0.0.1",
13
+ version: "0.0.2",
14
14
  type: "action",
15
15
  annotations: {
16
16
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-find-message",
5
5
  name: "Find Message",
6
6
  description: "Find a Slack message. [See the documentation](https://api.slack.com/methods/assistant.search.context)",
7
- version: "0.1.3",
7
+ version: "0.1.4",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-find-user-by-email",
5
5
  name: "Find User by Email",
6
6
  description: "Find a user by matching against their email. [See the documentation](https://api.slack.com/methods/users.lookupByEmail)",
7
- version: "0.0.28",
7
+ version: "0.0.29",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-find-user-by-id",
5
5
  name: "Find User by ID",
6
6
  description: "Find a user by their ID. Returns user profile information including name, email (requires `users:read.email` scope), timezone, and status. [See the documentation](https://api.slack.com/methods/users.info)",
7
- version: "0.0.4",
7
+ version: "0.0.6",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -3,8 +3,8 @@ import slack from "../../slack_v2.app.mjs";
3
3
  export default {
4
4
  key: "slack_v2-get-channel-details",
5
5
  name: "Get Channel Details",
6
- description: "Retrieve details for a Slack channel by selecting it or providing an ID. [See the documentation](https://api.slack.com/methods/conversations.info)",
7
- version: "0.0.4",
6
+ description: "Retrieve details for a Slack channel, specified by ID or by name — names are resolved automatically. [See the documentation](https://api.slack.com/methods/conversations.info)",
7
+ version: "0.1.0",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -36,8 +36,11 @@ export default {
36
36
  },
37
37
  },
38
38
  async run({ $ }) {
39
+ // Accept a channel NAME as well as an ID — agents routinely pass the "#name" they read
40
+ // in the prompt, and conversations.info answers that with channel_not_found.
41
+ const channelId = await this.slack.resolveChannelId(this.channel);
39
42
  const response = await this.slack.conversationsInfo({
40
- channel: this.channel,
43
+ channel: channelId,
41
44
  include_locale: this.includeLocale,
42
45
  include_num_members: this.includeNumberOfMembers,
43
46
  });
@@ -1,3 +1,4 @@
1
+ import utils from "../../common/utils.mjs";
1
2
  import slack from "../../slack_v2.app.mjs";
2
3
 
3
4
  export default {
@@ -9,8 +10,11 @@ export default {
9
10
  + " Use this when you want to see a channel's latest messages — unlike **Search** which finds messages by keyword."
10
11
  + " Returns messages with text, timestamps (ts), reactions, and user IDs."
11
12
  + " Message timestamps can be used with **Get Thread Replies**, **Edit Message**, and **Add Reaction**."
13
+ + " **Pass `fields`** (e.g. `text,ts,user`) unless you need full message objects — raw Slack"
14
+ + " messages carry blocks, attachments and edit metadata, so a busy channel can run to tens"
15
+ + " of thousands of characters and be truncated before you see any of it."
12
16
  + " [See the documentation](https://api.slack.com/methods/conversations.history)",
13
- version: "0.0.1",
17
+ version: "0.1.0",
14
18
  type: "action",
15
19
  annotations: {
16
20
  destructiveHint: false,
@@ -43,6 +47,12 @@ export default {
43
47
  description: "Only messages before this Unix timestamp. Default: now.",
44
48
  optional: true,
45
49
  },
50
+ fields: {
51
+ type: "string[]",
52
+ label: "Fields",
53
+ description: "Message properties to return, e.g. `text`, `ts`, `user`, `thread_ts`, `reply_count`, `reactions`. Recommended: `[\"text\", \"ts\", \"user\"]`. Omit only when you need the full message objects.",
54
+ optional: true,
55
+ },
46
56
  },
47
57
  async run({ $ }) {
48
58
  const channelId = await this.slack.resolveChannelId(this.channel);
@@ -54,11 +64,14 @@ export default {
54
64
  include_all_metadata: true,
55
65
  });
56
66
  const messages = response.messages || [];
67
+
57
68
  $.export("$summary", `Retrieved ${messages.length} message${messages.length === 1
58
69
  ? ""
59
70
  : "s"} from channel`);
60
71
  return {
61
- messages,
72
+ // `fields` is ADDITIVE: omitted returns exactly what this action always returned.
73
+ // Measured at 17k chars average (worst 49k) without it.
74
+ messages: utils.projectFields(messages, this.fields),
62
75
  };
63
76
  },
64
77
  };
@@ -3,8 +3,8 @@ import slack from "../../slack_v2.app.mjs";
3
3
  export default {
4
4
  key: "slack_v2-get-current-user",
5
5
  name: "Get Current User",
6
- description: "Retrieve comprehensive context about the authenticated Slack member, combining `auth.test`, `users.info`, `users.profile.get`, and `team.info` payloads. Returns the user’s profile (name variants, email, locale, timezone, status, admin flags), raw auth test data, and workspace metadata (domain, enterprise info, icons). Ideal when you need to confirm which user token is active, tailor messages to their locale/timezone, or ground an LLM in the member’s role and workspace before executing other Slack actions. [See Slack API docs](https://api.slack.com/methods/auth.test).",
7
- version: "0.0.4",
6
+ description: "Do NOT use for a plain \"who am I\" / \"what's my user ID\" question call **Get User Details** for that; it answers the same question with a far smaller payload. Use this ONLY when you specifically need the full member profile: locale, timezone, presence/status, admin and owner flags, name variants, or workspace domain and enterprise metadata. Combines `auth.test`, `users.info`, `users.profile.get` and `team.info`. [See Slack API docs](https://api.slack.com/methods/auth.test).",
7
+ version: "0.0.5",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "slack_v2-get-file",
6
6
  name: "Get File",
7
7
  description: "Return information about a file. [See the documentation](https://api.slack.com/methods/files.info)",
8
- version: "0.1.3",
8
+ version: "0.1.5",
9
9
  annotations: {
10
10
  destructiveHint: false,
11
11
  openWorldHint: true,
@@ -1,3 +1,4 @@
1
+ import utils from "../../common/utils.mjs";
1
2
  import slack from "../../slack_v2.app.mjs";
2
3
 
3
4
  export default {
@@ -8,8 +9,11 @@ export default {
8
9
  + " Accepts a channel ID or channel name (resolved automatically)."
9
10
  + " Use **Get Channel History** or **Search** to find the parent message's timestamp (thread_ts)."
10
11
  + " Returns the parent message followed by all replies in chronological order."
12
+ + " **Pass `fields`** (e.g. `text,ts,user`) unless you need full message objects — raw"
13
+ + " Slack messages carry blocks, attachments and edit metadata, so a long thread can run"
14
+ + " to tens of thousands of characters and be truncated before you see any of it."
11
15
  + " [See the documentation](https://api.slack.com/methods/conversations.replies)",
12
- version: "0.0.1",
16
+ version: "0.1.0",
13
17
  type: "action",
14
18
  annotations: {
15
19
  destructiveHint: false,
@@ -35,6 +39,12 @@ export default {
35
39
  default: 50,
36
40
  optional: true,
37
41
  },
42
+ fields: {
43
+ type: "string[]",
44
+ label: "Fields",
45
+ description: "Message properties to return, e.g. `text`, `ts`, `user`, `thread_ts`, `reply_count`, `reactions`, `permalink`. Recommended: `[\"text\", \"ts\", \"user\"]`. Omit only when you need the full message objects.",
46
+ optional: true,
47
+ },
38
48
  },
39
49
  async run({ $ }) {
40
50
  const channelId = await this.slack.resolveChannelId(this.channel);
@@ -45,11 +55,16 @@ export default {
45
55
  });
46
56
  const messages = response.messages || [];
47
57
  const replyCount = Math.max(messages.length - 1, 0);
58
+
48
59
  $.export("$summary", `Retrieved ${replyCount} repl${replyCount === 1
49
60
  ? "y"
50
61
  : "ies"} in thread`);
51
62
  return {
52
- messages,
63
+ // `fields` is ADDITIVE: omitted returns exactly what this action always returned.
64
+ // Supplied, it plucks per message — measured at 25k chars average on a busy thread,
65
+ // which is the difference between the agent reading the replies and being handed a
66
+ // file path instead.
67
+ messages: utils.projectFields(messages, this.fields),
53
68
  };
54
69
  },
55
70
  };
@@ -4,12 +4,16 @@ export default {
4
4
  key: "slack_v2-get-user-details",
5
5
  name: "Get User Details",
6
6
  description:
7
- "Retrieve the authenticated user's identity and workspace context."
7
+ "**Use when the user asks who am I, what's my user ID, what's my username, which workspace"
8
+ + " am I in, or what's my email** — this is the default identity lookup and the one to call"
9
+ + " first in any session."
8
10
  + " Returns user ID, name, email, timezone, profile, and workspace metadata."
9
- + " Call this first in any session to establish who you are other tools like"
10
- + " **Search** and **List Channels** can then filter by your user ID."
11
+ + " Other tools like **Search** and **List Channels** can then filter by your user ID,"
12
+ + " and **Post Message** can use it as the channel to DM yourself."
13
+ + " Prefer this over **Get Current User**, which returns a much larger payload and is only"
14
+ + " needed for full profile detail (locale, status, admin flags)."
11
15
  + " [See the documentation](https://api.slack.com/methods/auth.test)",
12
- version: "0.0.1",
16
+ version: "0.0.2",
13
17
  type: "action",
14
18
  annotations: {
15
19
  destructiveHint: false,
@@ -1,10 +1,11 @@
1
+ import { ConfigurationError } from "@pipedream/platform";
1
2
  import slack from "../../slack_v2.app.mjs";
2
3
 
3
4
  export default {
4
5
  key: "slack_v2-invite-user-to-channel",
5
6
  name: "Invite User to Channel",
6
- description: "Invite a user to an existing channel. [See the documentation](https://api.slack.com/methods/conversations.invite)",
7
- version: "0.0.28",
7
+ description: "Invite one or more users to an existing channel. Accepts a channel ID or NAME, and a user ID, EMAIL address or display name — all resolved automatically. Pass several users as a comma-separated list. [See the documentation](https://api.slack.com/methods/conversations.invite)",
8
+ version: "0.1.0",
8
9
  annotations: {
9
10
  destructiveHint: false,
10
11
  openWorldHint: true,
@@ -27,19 +28,76 @@ export default {
27
28
  },
28
29
  },
29
30
  async run({ $ }) {
30
- try {
31
- const response = await this.slack.inviteToConversation({
32
- channel: this.conversation,
33
- users: this.user,
34
- });
35
- $.export("$summary", `Successfully invited user ${this.user} to channel with ID ${this.conversation}`);
36
- return response;
37
- } catch (error) {
38
- if (`${error}`.includes("already_in_channel")) {
39
- $.export("$summary", `The user ${this.user} is already in the channel`);
40
- return;
31
+ // Agents pass whatever identifier the user said — an email in "invite
32
+ // dylan@pipedream.com", a "#channel-name" lifted from the prompt. The raw API answers
33
+ // user_not_found / channel_not_found, which reads as a broken tool rather than a
34
+ // wrong-shaped argument. Every AI-optimized tool in this app resolves names already.
35
+ //
36
+ // resolveUserIds (plural) so the comma-separated list Slack has always accepted here
37
+ // keeps working — each token is resolved independently.
38
+ const channel = await this.slack.resolveChannelId(this.conversation);
39
+ const user = await this.slack.resolveUserIds(this.user);
40
+ const userIds = user.split(",").filter(Boolean);
41
+
42
+ if (!userIds.length) {
43
+ throw new ConfigurationError(`No valid user was resolved from "${this.user}". Provide a user ID, an email address, or a display name.`);
44
+ }
45
+
46
+ // already_in_channel means the invite is a no-op because the user's already a member.
47
+ // cant_invite_self means the resolved user IS the calling identity — Slack refuses to
48
+ // let an identity invite itself, which reads the same way to the caller: the user is
49
+ // already effectively "in" the channel via that identity, no action needed.
50
+ const isNoopOutcome = (error) => `${error}`.includes("already_in_channel") || `${error}`.includes("cant_invite_self");
51
+
52
+ if (userIds.length === 1) {
53
+ try {
54
+ const response = await this.slack.inviteToConversation({
55
+ channel,
56
+ users: user,
57
+ });
58
+ $.export("$summary", `Successfully invited user ${this.user} to channel with ID ${channel}`);
59
+ return response;
60
+ } catch (error) {
61
+ if (isNoopOutcome(error)) {
62
+ $.export("$summary", `The user ${this.user} is already in the channel`);
63
+ return;
64
+ }
65
+ throw error;
41
66
  }
42
- throw error;
43
67
  }
68
+
69
+ // already_in_channel/cant_invite_self can only be safely swallowed for a single-user
70
+ // request. For multiple users, one bad token turning the whole request into "success"
71
+ // would hide that the others were never invited, so each user is invited individually
72
+ // and reported on its own.
73
+ const results = [];
74
+ for (const userId of userIds) {
75
+ try {
76
+ await this.slack.inviteToConversation({
77
+ channel,
78
+ users: userId,
79
+ });
80
+ results.push({
81
+ user: userId,
82
+ status: "invited",
83
+ });
84
+ } catch (error) {
85
+ results.push({
86
+ user: userId,
87
+ status: isNoopOutcome(error)
88
+ ? "already_in_channel"
89
+ : "error",
90
+ error: `${error}`,
91
+ });
92
+ }
93
+ }
94
+
95
+ const invited = results.filter(({ status }) => status === "invited").length;
96
+ const failed = results.filter(({ status }) => status === "error").length;
97
+ const failedSuffix = failed
98
+ ? ` (${failed} failed)`
99
+ : "";
100
+ $.export("$summary", `Invited ${invited} of ${userIds.length} user(s) to channel with ID ${channel}${failedSuffix}`);
101
+ return results;
44
102
  },
45
103
  };
@@ -5,7 +5,7 @@ export default {
5
5
  key: "slack_v2-kick-user",
6
6
  name: "Kick User",
7
7
  description: "Remove a user from a conversation. [See the documentation](https://api.slack.com/methods/conversations.kick)",
8
- version: "0.0.28",
8
+ version: "0.0.30",
9
9
  annotations: {
10
10
  destructiveHint: true,
11
11
  openWorldHint: true,
@@ -1,10 +1,20 @@
1
+ import utils from "../../common/utils.mjs";
1
2
  import slack from "../../slack_v2.app.mjs";
2
3
 
3
4
  export default {
4
5
  key: "slack_v2-list-channels",
5
6
  name: "List Channels",
6
- description: "Return a list of all channels in a workspace. [See the documentation](https://api.slack.com/methods/conversations.list)",
7
- version: "0.1.4",
7
+ description:
8
+ "Return a list of channels in a workspace."
9
+ + " **Always pass `fields`** with just the properties you need (e.g. `id,name`) —"
10
+ + " a full channel object is ~1KB, so a workspace of any real size returns a payload"
11
+ + " large enough to be truncated before you ever see it. `id,name` covers most tasks;"
12
+ + " every other tool here accepts a channel NAME and resolves it, so you rarely need more."
13
+ + " Returns `has_more: true` and `next_cursor` when more channels exist than were"
14
+ + " fetched — when you see that, raise `numPages` (or pass `cursor`) before answering"
15
+ + " any 'how many' or 'list every' question, otherwise your answer is silently incomplete."
16
+ + " [See the documentation](https://api.slack.com/methods/conversations.list)",
17
+ version: "0.2.0",
8
18
  annotations: {
9
19
  destructiveHint: false,
10
20
  openWorldHint: true,
@@ -34,6 +44,15 @@ export default {
34
44
  default: "public_channel",
35
45
  optional: true,
36
46
  },
47
+ fields: {
48
+ type: "string[]",
49
+ label: "Fields",
50
+ description:
51
+ "Channel properties to return, e.g. `id`, `name`, `is_private`, `is_archived`, `num_members`, `topic`, `purpose`, `created`."
52
+ + " Strongly recommended: `[\"id\", \"name\"]` is enough for almost every task and keeps the response small."
53
+ + " Omit ONLY when you genuinely need the full channel objects — the response is then ~1KB per channel and may be truncated.",
54
+ optional: true,
55
+ },
37
56
  pageSize: {
38
57
  propDefinition: [
39
58
  slack,
@@ -46,6 +65,12 @@ export default {
46
65
  "numPages",
47
66
  ],
48
67
  },
68
+ cursor: {
69
+ type: "string",
70
+ label: "Cursor",
71
+ description: "Resume from a previous call's `next_cursor` to fetch the following page.",
72
+ optional: true,
73
+ },
49
74
  },
50
75
  async run({ $ }) {
51
76
  const allChannels = [];
@@ -56,22 +81,45 @@ export default {
56
81
  limit: this.pageSize,
57
82
  types,
58
83
  };
84
+ if (this.cursor) params.cursor = this.cursor;
59
85
  let page = 0;
86
+ let nextCursor;
60
87
 
61
88
  do {
62
89
  const {
63
- channels, response_metadata: { next_cursor: nextCursor },
90
+ channels, response_metadata: metadata,
64
91
  } = await this.slack.conversationsList(params);
65
92
  allChannels.push(...channels);
93
+ nextCursor = metadata?.next_cursor;
66
94
  params.cursor = nextCursor;
67
95
  page++;
68
96
  } while (params.cursor && page < this.numPages);
69
97
 
70
- $.export("$summary", `Successfully found ${allChannels.length} channel${allChannels.length === 1
98
+ // `fields` is ADDITIVE: omitted returns exactly what this action has always
99
+ // returned, so existing workflows are unaffected. Supplied, it plucks per channel —
100
+ // the difference between ~1KB and ~40 bytes per row, which is what decides whether
101
+ // an agent receives the data or a "result too large" file path.
102
+ const channels = utils.projectFields(allChannels, this.fields);
103
+
104
+ // Truncation must be VISIBLE. numPages defaults to 1, so the previous version
105
+ // silently dropped every channel past the first page and the caller had no way to
106
+ // know the list was partial — a confidently wrong answer to "list every channel".
107
+ const hasMore = Boolean(nextCursor);
108
+
109
+ $.export("$summary", `Successfully found ${channels.length} channel${channels.length === 1
71
110
  ? ""
72
- : "s"}`);
111
+ : "s"}${hasMore
112
+ ? " (more available — raise Number of Pages or pass the cursor)"
113
+ : ""}`);
114
+
73
115
  return {
74
- channels: allChannels,
116
+ channels,
117
+ has_more: hasMore,
118
+ ...(hasMore
119
+ ? {
120
+ next_cursor: nextCursor,
121
+ }
122
+ : {}),
75
123
  };
76
124
  },
77
125
  };
@@ -5,7 +5,7 @@ export default {
5
5
  name: "List Emojis",
6
6
  description:
7
7
  "List all available emojis in the Slack workspace. Optionally include emoji image URLs. [See the documentation](https://api.slack.com/methods/emoji.list)",
8
- version: "0.0.3",
8
+ version: "0.0.4",
9
9
  annotations: {
10
10
  destructiveHint: false,
11
11
  openWorldHint: true,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "slack_v2-list-files",
6
6
  name: "List Files",
7
7
  description: "Return a list of files within a team. [See the documentation](https://api.slack.com/methods/files.list)",
8
- version: "0.1.3",
8
+ version: "0.1.5",
9
9
  annotations: {
10
10
  destructiveHint: false,
11
11
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-list-group-members",
5
5
  name: "List Group Members",
6
6
  description: "List all users in a User Group. [See the documentation](https://api.slack.com/methods/usergroups.users.list)",
7
- version: "0.0.13",
7
+ version: "0.0.14",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "slack_v2-list-icon-emoji-options",
5
5
  name: "List Icon (emoji) Options",
6
6
  description: "Retrieves available options for the Icon (emoji) field.",
7
- version: "0.0.1",
7
+ version: "0.0.2",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,