Skip to content

Commit 72d067c

Browse files
committed
Merge remote-tracking branch 'origin/staging' into codex/pr-7477
# Conflicts: # apps/docs/content/docs/integrations/slack.mdx # apps/sim/tools/generated/tool-metadata.ts # apps/sim/tools/generated/tool-outputs.ts # apps/sim/tools/slack/list_channels.test.ts # apps/sim/tools/slack/list_channels.ts # packages/deployment-config/src/integrations.json
2 parents 3b63c42 + 04322e0 commit 72d067c

13 files changed

Lines changed: 674 additions & 316 deletions

File tree

apps/docs/content/docs/integrations/slack.mdx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ Rename the Slack agent session associated with a thread.
956956

957957
### Slack List Channels
958958

959-
List accessible public and private Slack channels.
959+
List up to 10,000 accessible public and private Slack channels across as many cursor pages as Slack supplies, capped at 200 provider pages.
960960

961961
#### Input
962962

@@ -966,14 +966,15 @@ List accessible public and private Slack channels.
966966
| `botToken` | string | No | Bot token for Custom Bot |
967967
| `includePrivate` | boolean | No | Include private channels the connected account can access \(default: true\) |
968968
| `excludeArchived` | boolean | No | Exclude archived channels \(default: true\) |
969-
| `limit` | number | No | Maximum number of channels to return \(default: 100, max: 200\) |
970-
| `cursor` | string | No | Pagination cursor from a previous response.next_cursor |
969+
| `limit` | number | No | Conversations to request per Slack page \(default: 100, max: 200\) |
970+
| `cursor` | string | No | Pagination cursor from a previous response.nextCursor to resume from |
971+
| `maxPages` | number | No | Maximum number of Slack pages to fetch \(default: 200, max: 200\) |
971972

972973
#### Output
973974

974975
| Parameter | Type | Description |
975976
| --------- | ---- | ----------- |
976-
| `channels` | array | Accessible public and private channels |
977+
| `channels` | array | Up to 10,000 accessible public and private channels |
977978
|`id` | string | Conversation ID \(for example, C123, D123, or G123\) |
978979
|`name` | string | Channel or group-DM name; omitted for one-to-one direct messages |
979980
|`is_channel` | boolean | Whether this is a channel |
@@ -999,8 +1000,10 @@ List accessible public and private Slack channels.
9991000
|`priority` | number | Slack sidebar sort priority |
10001001
| `ids` | array | Conversation IDs for every returned channel |
10011002
| `names` | array | Names of returned channels |
1002-
| `count` | number | Total number of conversations returned |
1003-
| `nextCursor` | string | Cursor for the next page; null if no more pages |
1003+
| `count` | number | Total number of conversations returned across all fetched pages, up to 10,000 |
1004+
| `hasMore` | boolean | Whether more Slack conversation pages remain beyond the fetched window |
1005+
| `nextCursor` | string | Cursor to fetch the next page; null when there are no more pages |
1006+
| `pages` | number | Number of Slack conversation pages fetched in this invocation |
10041007

10051008
### Slack List Channel Members
10061009

apps/sim/blocks/blocks/slack.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,34 @@ describe('Slack block release', () => {
189189
expect(isSlackV2SubBlockVisible('agentChannel', repurposedValues)).toBe(true)
190190
expect(selectTool(repurposedValues)).toBe('slack_set_suggested_prompts_v2')
191191
})
192+
193+
it('maps bounded cursor pagination for list channels', () => {
194+
const values = { operation: 'list_channels' }
195+
expect(SlackV2Block.outputs.hasMore.description).toBe(
196+
'Whether more thread messages or provider pages remain beyond the fetched window'
197+
)
198+
expect(isSlackV2SubBlockVisible('channelMaxPages', values)).toBe(true)
199+
expect(isSlackV2SubBlockVisible('paginationCursor', values)).toBe(true)
200+
expect(
201+
mapSlackV2Params({
202+
...values,
203+
channelLimit: '50',
204+
channelMaxPages: '4',
205+
paginationCursor: ' cursor-1 ',
206+
})
207+
).toMatchObject({
208+
limit: 50,
209+
maxPages: 4,
210+
cursor: 'cursor-1',
211+
})
212+
expect(() => mapSlackV2Params({ ...values, channelLimit: '201' })).toThrow(
213+
'Conversations per page must be an integer between 1 and 200'
214+
)
215+
expect(() => mapSlackV2Params({ ...values, channelMaxPages: '201' })).toThrow(
216+
'Max pages must be an integer between 1 and 200'
217+
)
218+
expect(mapSlackV2Params({ ...values, channelLimit: null, channelMaxPages: ' ' })).toMatchObject(
219+
{ limit: 100 }
220+
)
221+
})
192222
})

apps/sim/blocks/blocks/slack.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
163163
{ text: ', with heading', field: 'promptsTitle' },
164164
],
165165
list_channels: [
166+
'List Slack conversations',
166167
{
167-
text: 'List up to',
168+
text: ', in pages of',
168169
field: 'channelLimit',
169-
after: 'channels',
170-
core: true,
170+
after: 'items',
171171
},
172172
],
173173
list_members: [
@@ -762,13 +762,25 @@ Do not include any explanations, markdown formatting, or other text outside the
762762
},
763763
{
764764
id: 'channelLimit',
765-
title: 'Channel Limit',
765+
title: 'Conversations Per Page',
766766
type: 'short-input',
767767
placeholder: '100',
768768
condition: {
769769
field: 'operation',
770770
value: 'list_channels',
771771
},
772+
mode: 'advanced',
773+
},
774+
{
775+
id: 'channelMaxPages',
776+
title: 'Max Pages',
777+
type: 'short-input',
778+
placeholder: '200',
779+
condition: {
780+
field: 'operation',
781+
value: 'list_channels',
782+
},
783+
mode: 'advanced',
772784
},
773785
// List Members specific fields
774786
{
@@ -1911,6 +1923,7 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
19111923
emojiName,
19121924
includePrivate,
19131925
channelLimit,
1926+
channelMaxPages,
19141927
memberLimit,
19151928
includeDeleted,
19161929
userLimit,
@@ -2127,7 +2140,26 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
21272140
case 'list_channels': {
21282141
baseParams.includePrivate = includePrivate !== 'false'
21292142
baseParams.excludeArchived = true
2130-
baseParams.limit = channelLimit ? Number.parseInt(channelLimit, 10) : 100
2143+
const hasChannelLimit =
2144+
channelLimit !== undefined &&
2145+
channelLimit !== null &&
2146+
(typeof channelLimit !== 'string' || Boolean(channelLimit.trim()))
2147+
const parsedLimit = hasChannelLimit ? Number(channelLimit) : 100
2148+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 200) {
2149+
throw new Error('Conversations per page must be an integer between 1 and 200')
2150+
}
2151+
baseParams.limit = parsedLimit
2152+
const hasChannelMaxPages =
2153+
channelMaxPages !== undefined &&
2154+
channelMaxPages !== null &&
2155+
(typeof channelMaxPages !== 'string' || Boolean(channelMaxPages.trim()))
2156+
if (hasChannelMaxPages) {
2157+
const parsedMaxPages = Number(channelMaxPages)
2158+
if (!Number.isInteger(parsedMaxPages) || parsedMaxPages < 1 || parsedMaxPages > 200) {
2159+
throw new Error('Max pages must be an integer between 1 and 200')
2160+
}
2161+
baseParams.maxPages = parsedMaxPages
2162+
}
21312163
if (paginationCursor) {
21322164
baseParams.cursor = String(paginationCursor).trim()
21332165
}
@@ -2393,7 +2425,8 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
23932425
thread_ts: { type: 'string', description: 'Thread timestamp for reply' },
23942426
// List Channels inputs
23952427
includePrivate: { type: 'string', description: 'Include private channels (true/false)' },
2396-
channelLimit: { type: 'string', description: 'Maximum number of channels to return' },
2428+
channelLimit: { type: 'string', description: 'Conversations to request per Slack page' },
2429+
channelMaxPages: { type: 'string', description: 'Maximum Slack pages to fetch (max 200)' },
23972430
// List Members inputs
23982431
memberLimit: { type: 'string', description: 'Maximum number of members to return' },
23992432
// List Users inputs
@@ -2600,13 +2633,14 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
26002633
},
26012634
hasMore: {
26022635
type: 'boolean',
2603-
description: 'Whether there are more messages in the thread',
2636+
description:
2637+
'Whether more thread messages or provider pages remain beyond the fetched window',
26042638
},
26052639

26062640
// slack_get_channel_history / slack_get_thread_replies pagination outputs
26072641
pages: {
26082642
type: 'number',
2609-
description: 'Number of pages fetched during a paginated history/replies read',
2643+
description: 'Number of provider pages fetched during a paginated read',
26102644
},
26112645
threadTs: {
26122646
type: 'string',
@@ -2623,7 +2657,7 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
26232657
channels: {
26242658
type: 'json',
26252659
description:
2626-
'Array of accessible conversation objects. Credential-group user tokens also include direct and group DMs, with type fields (is_channel, is_im, is_mpim) and DM participant field user.',
2660+
'Array of up to 10,000 accessible public and private channel objects, including conversation type and membership fields.',
26272661
},
26282662
count: {
26292663
type: 'number',

apps/sim/lib/internal/slack/execute-tool.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ const mocks = vi.hoisted(() => ({
88
addReaction: vi.fn(),
99
deleteMessage: vi.fn(),
1010
download: vi.fn(),
11+
listConversations: vi.fn(),
1112
readMessages: vi.fn(),
1213
removeReaction: vi.fn(),
1314
sendEphemeral: vi.fn(),
1415
sendMessage: vi.fn(),
1516
updateMessage: vi.fn(),
1617
}))
1718

19+
vi.mock('@/lib/internal/slack/operations/list-conversations', () => ({
20+
executeSlackListConversationsOperation: mocks.listConversations,
21+
}))
22+
1823
vi.mock('@/lib/internal/slack/operations', () => ({
1924
executeSlackAddReaction: mocks.addReaction,
2025
executeSlackDeleteMessage: mocks.deleteMessage,
@@ -40,6 +45,7 @@ const INPUTS = {
4045
},
4146
slack_delete_message: { accessToken: 'token', channel: 'C1', timestamp: '1.0' },
4247
slack_download: { accessToken: 'token', fileId: 'F1', fileName: 'report.pdf' },
48+
slack_list_channels: { accessToken: 'token', limit: 100, maxPages: 10 },
4349
slack_ephemeral_message: {
4450
accessToken: 'token',
4551
channel: 'C1',
@@ -66,6 +72,7 @@ const DISPATCH = {
6672
slack_add_reaction: mocks.addReaction,
6773
slack_delete_message: mocks.deleteMessage,
6874
slack_download: mocks.download,
75+
slack_list_channels: mocks.listConversations,
6976
slack_ephemeral_message: mocks.sendEphemeral,
7077
slack_message: mocks.sendMessage,
7178
slack_message_reader: mocks.readMessages,
@@ -115,6 +122,13 @@ describe('executeSlackTool', () => {
115122
signal: controller.signal,
116123
userId: 'user-1',
117124
})
125+
} else if (toolId === 'slack_list_channels') {
126+
expect(DISPATCH[toolId].mock.calls[0]?.[1]).toBe(controller.signal)
127+
expect(DISPATCH[toolId].mock.calls[0]?.[2]).toMatchObject({
128+
workflowId: 'workflow-1',
129+
workspaceId: 'workspace-1',
130+
userId: 'user-1',
131+
})
118132
} else {
119133
expect(DISPATCH[toolId].mock.calls[0]?.[1]).toBe(controller.signal)
120134
}

apps/sim/lib/internal/slack/execute-tool.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from '@/lib/internal/slack/operations'
2727
import { executeSlackGetChannelHistoryOperation } from '@/lib/internal/slack/operations/get-channel-history'
2828
import { executeSlackGetThreadRepliesOperation } from '@/lib/internal/slack/operations/get-thread-replies'
29+
import { executeSlackListConversationsOperation } from '@/lib/internal/slack/operations/list-conversations'
2930
import { executeToolOperationImplementation } from '@/lib/internal/tool-operations/execute'
3031
import { parseInternalToolInput } from '@/lib/internal/tool-operations/parse-input'
3132
import type {
@@ -94,6 +95,8 @@ export const executeSlackTool: InternalToolOperationHandler = async (request) =>
9495
return executeToolOperationImplementation(executeSlackGetChannelHistoryOperation, request)
9596
case 'slack_get_thread_replies':
9697
return executeToolOperationImplementation(executeSlackGetThreadRepliesOperation, request)
98+
case 'slack_list_channels':
99+
return executeToolOperationImplementation(executeSlackListConversationsOperation, request)
97100
case 'slack_ephemeral_message':
98101
return executeOperation(slackSendEphemeralContract, request, (input) =>
99102
executeSlackSendEphemeral(input, request.signal)

0 commit comments

Comments
 (0)