From 8e77cb0aa399364a2c58beb0ee0015ad06e7da31 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 18:53:42 -0800 Subject: [PATCH 1/9] feat(slack): add new tools and user selectors --- apps/docs/content/docs/en/tools/slack.mdx | 102 ++++++- apps/sim/blocks/blocks/slack.ts | 279 +++++++++++++++++- apps/sim/tools/registry.ts | 8 + apps/sim/tools/slack/create_channel_canvas.ts | 108 +++++++ apps/sim/tools/slack/edit_canvas.ts | 121 ++++++++ apps/sim/tools/slack/get_channel_info.ts | 115 ++++++++ apps/sim/tools/slack/get_user_presence.ts | 117 ++++++++ apps/sim/tools/slack/index.ts | 8 + apps/sim/tools/slack/types.ts | 56 ++++ 9 files changed, 909 insertions(+), 5 deletions(-) create mode 100644 apps/sim/tools/slack/create_channel_canvas.ts create mode 100644 apps/sim/tools/slack/edit_canvas.ts create mode 100644 apps/sim/tools/slack/get_channel_info.ts create mode 100644 apps/sim/tools/slack/get_user_presence.ts diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx index 892ee6d2d31..782cff06301 100644 --- a/apps/docs/content/docs/en/tools/slack.mdx +++ b/apps/docs/content/docs/en/tools/slack.mdx @@ -1,6 +1,6 @@ --- title: Slack -description: Send, update, delete messages, send ephemeral messages, add or remove reactions in Slack or trigger workflows from Slack events +description: Send, update, delete messages, add or remove reactions, manage canvases, get channel info and user presence in Slack --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -823,4 +823,104 @@ Remove an emoji reaction from a Slack message | ↳ `timestamp` | string | Message timestamp | | ↳ `reaction` | string | Emoji reaction name | +### `slack_get_channel_info` + +Get detailed information about a Slack channel by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `authMethod` | string | No | Authentication method: oauth or bot_token | +| `botToken` | string | No | Bot token for Custom Bot | +| `channel` | string | Yes | Channel ID to get information about \(e.g., C1234567890\) | +| `includeNumMembers` | boolean | No | Whether to include the member count in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `channel` | object | Detailed channel information | +| ↳ `id` | string | Channel ID \(e.g., C1234567890\) | +| ↳ `name` | string | Channel name without # prefix | +| ↳ `is_channel` | boolean | Whether this is a channel | +| ↳ `is_private` | boolean | Whether channel is private | +| ↳ `is_archived` | boolean | Whether channel is archived | +| ↳ `is_general` | boolean | Whether this is the general channel | +| ↳ `is_member` | boolean | Whether the bot/user is a member | +| ↳ `is_shared` | boolean | Whether channel is shared across workspaces | +| ↳ `is_ext_shared` | boolean | Whether channel is externally shared | +| ↳ `is_org_shared` | boolean | Whether channel is org-wide shared | +| ↳ `num_members` | number | Number of members in the channel | +| ↳ `topic` | string | Channel topic | +| ↳ `purpose` | string | Channel purpose/description | +| ↳ `created` | number | Unix timestamp when channel was created | +| ↳ `creator` | string | User ID of channel creator | +| ↳ `updated` | number | Unix timestamp of last update | + +### `slack_get_user_presence` + +Check whether a Slack user is currently active or away + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `authMethod` | string | No | Authentication method: oauth or bot_token | +| `botToken` | string | No | Bot token for Custom Bot | +| `userId` | string | Yes | User ID to check presence for \(e.g., U1234567890\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `presence` | string | User presence status: "active" or "away" | +| `online` | boolean | Whether user has an active client connection | +| `autoAway` | boolean | Whether user was automatically set to away due to inactivity | +| `manualAway` | boolean | Whether user manually set themselves as away | +| `connectionCount` | number | Total number of active connections for the user | +| `lastActivity` | number | Unix timestamp of last detected activity | + +### `slack_edit_canvas` + +Edit an existing Slack canvas by inserting, replacing, or deleting content + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `authMethod` | string | No | Authentication method: oauth or bot_token | +| `botToken` | string | No | Bot token for Custom Bot | +| `canvasId` | string | Yes | Canvas ID to edit \(e.g., F1234ABCD\) | +| `operation` | string | Yes | Edit operation: insert_at_start, insert_at_end, insert_after, insert_before, replace, delete, or rename | +| `content` | string | No | Markdown content for the operation \(required for insert/replace operations\) | +| `sectionId` | string | No | Section ID to target \(required for insert_after, insert_before, replace, and delete\) | +| `title` | string | No | New title for the canvas \(only used with rename operation\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Success message | + +### `slack_create_channel_canvas` + +Create a canvas pinned to a Slack channel as its resource hub + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `authMethod` | string | No | Authentication method: oauth or bot_token | +| `botToken` | string | No | Bot token for Custom Bot | +| `channel` | string | Yes | Channel ID to create the canvas in \(e.g., C1234567890\) | +| `title` | string | No | Title for the channel canvas | +| `content` | string | No | Canvas content in markdown format | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `canvas_id` | string | ID of the created channel canvas | + diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index f3d9d8f85b0..db05e44ad18 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -9,7 +9,7 @@ export const SlackBlock: BlockConfig = { type: 'slack', name: 'Slack', description: - 'Send, update, delete messages, send ephemeral messages, add or remove reactions in Slack or trigger workflows from Slack events', + 'Send, update, delete messages, add or remove reactions, manage canvases, get channel info and user presence in Slack', authMode: AuthMode.OAuth, longDescription: 'Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add or remove reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.', @@ -39,6 +39,10 @@ export const SlackBlock: BlockConfig = { { label: 'Delete Message', id: 'delete' }, { label: 'Add Reaction', id: 'react' }, { label: 'Remove Reaction', id: 'unreact' }, + { label: 'Get Channel Info', id: 'get_channel_info' }, + { label: 'Get User Presence', id: 'get_user_presence' }, + { label: 'Edit Canvas', id: 'edit_canvas' }, + { label: 'Create Channel Canvas', id: 'create_channel_canvas' }, ], value: () => 'send', }, @@ -142,7 +146,7 @@ export const SlackBlock: BlockConfig = { } return { field: 'operation', - value: ['list_channels', 'list_users', 'get_user'], + value: ['list_channels', 'list_users', 'get_user', 'get_user_presence', 'edit_canvas'], not: true, and: { field: 'destinationType', @@ -167,7 +171,7 @@ export const SlackBlock: BlockConfig = { } return { field: 'operation', - value: ['list_channels', 'list_users', 'get_user'], + value: ['list_channels', 'list_users', 'get_user', 'get_user_presence', 'edit_canvas'], not: true, and: { field: 'destinationType', @@ -210,8 +214,26 @@ export const SlackBlock: BlockConfig = { { id: 'ephemeralUser', title: 'Target User', + type: 'user-selector', + canonicalParamId: 'ephemeralUser', + serviceId: 'slack', + selectorKey: 'slack.users', + placeholder: 'Select Slack user', + mode: 'basic', + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + condition: { + field: 'operation', + value: 'ephemeral', + }, + required: true, + }, + { + id: 'manualEphemeralUser', + title: 'Target User ID', type: 'short-input', - placeholder: 'User ID who will see the message (e.g., U1234567890)', + canonicalParamId: 'ephemeralUser', + placeholder: 'Enter Slack user ID (e.g., U1234567890)', + mode: 'advanced', condition: { field: 'operation', value: 'ephemeral', @@ -441,9 +463,27 @@ Do not include any explanations, markdown formatting, or other text outside the // Get User specific fields { id: 'userId', + title: 'User', + type: 'user-selector', + canonicalParamId: 'userId', + serviceId: 'slack', + selectorKey: 'slack.users', + placeholder: 'Select Slack user', + mode: 'basic', + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + condition: { + field: 'operation', + value: 'get_user', + }, + required: true, + }, + { + id: 'manualUserId', title: 'User ID', type: 'short-input', + canonicalParamId: 'userId', placeholder: 'Enter Slack user ID (e.g., U1234567890)', + mode: 'advanced', condition: { field: 'operation', value: 'get_user', @@ -624,6 +664,146 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, required: true, }, + // Get Channel Info specific fields + { + id: 'includeNumMembers', + title: 'Include Member Count', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'true', + condition: { + field: 'operation', + value: 'get_channel_info', + }, + }, + // Get User Presence specific fields + { + id: 'presenceUserId', + title: 'User', + type: 'user-selector', + canonicalParamId: 'presenceUserId', + serviceId: 'slack', + selectorKey: 'slack.users', + placeholder: 'Select Slack user', + mode: 'basic', + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + condition: { + field: 'operation', + value: 'get_user_presence', + }, + required: true, + }, + { + id: 'manualPresenceUserId', + title: 'User ID', + type: 'short-input', + canonicalParamId: 'presenceUserId', + placeholder: 'Enter Slack user ID (e.g., U1234567890)', + mode: 'advanced', + condition: { + field: 'operation', + value: 'get_user_presence', + }, + required: true, + }, + // Edit Canvas specific fields + { + id: 'editCanvasId', + title: 'Canvas ID', + type: 'short-input', + placeholder: 'Enter canvas ID (e.g., F1234ABCD)', + condition: { + field: 'operation', + value: 'edit_canvas', + }, + required: true, + }, + { + id: 'canvasOperation', + title: 'Edit Operation', + type: 'dropdown', + options: [ + { label: 'Insert at Start', id: 'insert_at_start' }, + { label: 'Insert at End', id: 'insert_at_end' }, + { label: 'Insert After Section', id: 'insert_after' }, + { label: 'Insert Before Section', id: 'insert_before' }, + { label: 'Replace Section', id: 'replace' }, + { label: 'Delete Section', id: 'delete' }, + { label: 'Rename Canvas', id: 'rename' }, + ], + value: () => 'insert_at_end', + condition: { + field: 'operation', + value: 'edit_canvas', + }, + required: true, + }, + { + id: 'canvasContent', + title: 'Content', + type: 'long-input', + placeholder: 'Enter content in markdown format', + condition: { + field: 'operation', + value: 'edit_canvas', + and: { + field: 'canvasOperation', + value: ['delete', 'rename'], + not: true, + }, + }, + }, + { + id: 'sectionId', + title: 'Section ID', + type: 'short-input', + placeholder: 'Section ID to target', + condition: { + field: 'operation', + value: 'edit_canvas', + and: { + field: 'canvasOperation', + value: ['insert_after', 'insert_before', 'replace', 'delete'], + }, + }, + required: true, + }, + { + id: 'canvasTitle', + title: 'New Title', + type: 'short-input', + placeholder: 'Enter new canvas title', + condition: { + field: 'operation', + value: 'edit_canvas', + and: { field: 'canvasOperation', value: 'rename' }, + }, + required: true, + }, + // Create Channel Canvas specific fields + { + id: 'channelCanvasTitle', + title: 'Canvas Title', + type: 'short-input', + placeholder: 'Enter canvas title (optional)', + condition: { + field: 'operation', + value: 'create_channel_canvas', + }, + }, + { + id: 'channelCanvasContent', + title: 'Canvas Content', + type: 'long-input', + placeholder: 'Enter canvas content (markdown supported)', + condition: { + field: 'operation', + value: 'create_channel_canvas', + }, + }, ...getTrigger('slack_webhook').subBlocks, ], tools: { @@ -643,6 +823,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 'slack_delete_message', 'slack_add_reaction', 'slack_remove_reaction', + 'slack_get_channel_info', + 'slack_get_user_presence', + 'slack_edit_canvas', + 'slack_create_channel_canvas', ], config: { tool: (params) => { @@ -677,6 +861,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, return 'slack_add_reaction' case 'unreact': return 'slack_remove_reaction' + case 'get_channel_info': + return 'slack_get_channel_info' + case 'get_user_presence': + return 'slack_get_user_presence' + case 'edit_canvas': + return 'slack_edit_canvas' + case 'create_channel_canvas': + return 'slack_create_channel_canvas' default: throw new Error(`Invalid Slack operation: ${params.operation}`) } @@ -714,6 +906,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, getMessageTimestamp, getThreadTimestamp, threadLimit, + includeNumMembers, + presenceUserId, + editCanvasId, + canvasOperation, + canvasContent, + sectionId, + canvasTitle, + channelCanvasTitle, + channelCanvasContent, ...rest } = params @@ -849,6 +1050,37 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, baseParams.timestamp = reactionTimestamp baseParams.name = emojiName break + + case 'get_channel_info': + baseParams.includeNumMembers = includeNumMembers !== 'false' + break + + case 'get_user_presence': + baseParams.userId = presenceUserId + break + + case 'edit_canvas': + baseParams.canvasId = editCanvasId + baseParams.operation = canvasOperation + if (canvasContent) { + baseParams.content = canvasContent + } + if (sectionId) { + baseParams.sectionId = sectionId + } + if (canvasTitle) { + baseParams.title = canvasTitle + } + break + + case 'create_channel_canvas': + if (channelCanvasTitle) { + baseParams.title = channelCanvasTitle + } + if (channelCanvasContent) { + baseParams.content = channelCanvasContent + } + break } return baseParams @@ -903,6 +1135,19 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, type: 'string', description: 'Maximum number of messages to return from thread', }, + // Get Channel Info inputs + includeNumMembers: { type: 'string', description: 'Include member count (true/false)' }, + // Get User Presence inputs + presenceUserId: { type: 'string', description: 'User ID to check presence for' }, + // Edit Canvas inputs + editCanvasId: { type: 'string', description: 'Canvas ID to edit' }, + canvasOperation: { type: 'string', description: 'Canvas edit operation' }, + canvasContent: { type: 'string', description: 'Markdown content for canvas edit' }, + sectionId: { type: 'string', description: 'Canvas section ID to target' }, + canvasTitle: { type: 'string', description: 'New canvas title for rename' }, + // Create Channel Canvas inputs + channelCanvasTitle: { type: 'string', description: 'Title for channel canvas' }, + channelCanvasContent: { type: 'string', description: 'Content for channel canvas' }, }, outputs: { // slack_message outputs (send operation) @@ -999,6 +1244,32 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, description: 'Updated message metadata (legacy, use message object instead)', }, + // slack_get_user_presence outputs (get_user_presence operation) + presence: { + type: 'string', + description: 'User presence status: "active" or "away"', + }, + online: { + type: 'boolean', + description: 'Whether user has an active client connection', + }, + autoAway: { + type: 'boolean', + description: 'Whether user was automatically set to away', + }, + manualAway: { + type: 'boolean', + description: 'Whether user manually set themselves as away', + }, + connectionCount: { + type: 'number', + description: 'Total number of active connections', + }, + lastActivity: { + type: 'number', + description: 'Unix timestamp of last detected activity', + }, + // Trigger outputs (when used as webhook trigger) event_type: { type: 'string', description: 'Type of Slack event that triggered the workflow' }, channel_name: { type: 'string', description: 'Human-readable channel name' }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 72b480a5780..8015599df29 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1797,11 +1797,15 @@ import { import { slackAddReactionTool, slackCanvasTool, + slackCreateChannelCanvasTool, slackDeleteMessageTool, slackDownloadTool, + slackEditCanvasTool, slackEphemeralMessageTool, + slackGetChannelInfoTool, slackGetMessageTool, slackGetThreadTool, + slackGetUserPresenceTool, slackGetUserTool, slackListChannelsTool, slackListMembersTool, @@ -2613,6 +2617,10 @@ export const tools: Record = { slack_delete_message: slackDeleteMessageTool, slack_add_reaction: slackAddReactionTool, slack_remove_reaction: slackRemoveReactionTool, + slack_get_channel_info: slackGetChannelInfoTool, + slack_get_user_presence: slackGetUserPresenceTool, + slack_edit_canvas: slackEditCanvasTool, + slack_create_channel_canvas: slackCreateChannelCanvasTool, github_repo_info: githubRepoInfoTool, github_repo_info_v2: githubRepoInfoV2Tool, github_latest_commit: githubLatestCommitTool, diff --git a/apps/sim/tools/slack/create_channel_canvas.ts b/apps/sim/tools/slack/create_channel_canvas.ts new file mode 100644 index 00000000000..2544d02c9df --- /dev/null +++ b/apps/sim/tools/slack/create_channel_canvas.ts @@ -0,0 +1,108 @@ +import type { + SlackCreateChannelCanvasParams, + SlackCreateChannelCanvasResponse, +} from '@/tools/slack/types' +import type { ToolConfig } from '@/tools/types' + +export const slackCreateChannelCanvasTool: ToolConfig< + SlackCreateChannelCanvasParams, + SlackCreateChannelCanvasResponse +> = { + id: 'slack_create_channel_canvas', + name: 'Slack Create Channel Canvas', + description: 'Create a canvas pinned to a Slack channel as its resource hub', + version: '1.0.0', + + oauth: { + required: true, + provider: 'slack', + }, + + params: { + authMethod: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication method: oauth or bot_token', + }, + botToken: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Bot token for Custom Bot', + }, + accessToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'OAuth access token or bot token for Slack API', + }, + channel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Channel ID to create the canvas in (e.g., C1234567890)', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Title for the channel canvas', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Canvas content in markdown format', + }, + }, + + request: { + url: 'https://slack.com/api/conversations.canvases.create', + method: 'POST', + headers: (params: SlackCreateChannelCanvasParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.accessToken || params.botToken}`, + }), + body: (params: SlackCreateChannelCanvasParams) => { + const body: Record = { + channel_id: params.channel.trim(), + } + + if (params.title) { + body.title = params.title + } + + if (params.content) { + body.document_content = { + type: 'markdown', + markdown: params.content, + } + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.ok) { + if (data.error === 'channel_canvas_already_exists') { + throw new Error('A canvas already exists for this channel. Use Edit Canvas to modify it.') + } + throw new Error(data.error || 'Failed to create channel canvas') + } + + return { + success: true, + output: { + canvas_id: data.canvas_id, + }, + } + }, + + outputs: { + canvas_id: { type: 'string', description: 'ID of the created channel canvas' }, + }, +} diff --git a/apps/sim/tools/slack/edit_canvas.ts b/apps/sim/tools/slack/edit_canvas.ts new file mode 100644 index 00000000000..7b6eec5911f --- /dev/null +++ b/apps/sim/tools/slack/edit_canvas.ts @@ -0,0 +1,121 @@ +import type { SlackEditCanvasParams, SlackEditCanvasResponse } from '@/tools/slack/types' +import type { ToolConfig } from '@/tools/types' + +export const slackEditCanvasTool: ToolConfig = { + id: 'slack_edit_canvas', + name: 'Slack Edit Canvas', + description: 'Edit an existing Slack canvas by inserting, replacing, or deleting content', + version: '1.0.0', + + oauth: { + required: true, + provider: 'slack', + }, + + params: { + authMethod: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication method: oauth or bot_token', + }, + botToken: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Bot token for Custom Bot', + }, + accessToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'OAuth access token or bot token for Slack API', + }, + canvasId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Canvas ID to edit (e.g., F1234ABCD)', + }, + operation: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Edit operation: insert_at_start, insert_at_end, insert_after, insert_before, replace, delete, or rename', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Markdown content for the operation (required for insert/replace operations)', + }, + sectionId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Section ID to target (required for insert_after, insert_before, replace, and delete)', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New title for the canvas (only used with rename operation)', + }, + }, + + request: { + url: 'https://slack.com/api/canvases.edit', + method: 'POST', + headers: (params: SlackEditCanvasParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.accessToken || params.botToken}`, + }), + body: (params: SlackEditCanvasParams) => { + const change: Record = { + operation: params.operation, + } + + if (params.sectionId) { + change.section_id = params.sectionId.trim() + } + + if (params.operation === 'rename' && params.title) { + change.title_content = { + type: 'plain_text', + text: params.title, + } + } else if (params.content && params.operation !== 'delete') { + change.document_content = { + type: 'markdown', + markdown: params.content, + } + } + + return { + canvas_id: params.canvasId.trim(), + changes: [change], + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.ok) { + throw new Error(data.error || 'Failed to edit canvas') + } + + return { + success: true, + output: { + content: 'Successfully edited canvas', + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Success message' }, + }, +} diff --git a/apps/sim/tools/slack/get_channel_info.ts b/apps/sim/tools/slack/get_channel_info.ts new file mode 100644 index 00000000000..5a7485df9c0 --- /dev/null +++ b/apps/sim/tools/slack/get_channel_info.ts @@ -0,0 +1,115 @@ +import type { SlackGetChannelInfoParams, SlackGetChannelInfoResponse } from '@/tools/slack/types' +import { CHANNEL_OUTPUT_PROPERTIES } from '@/tools/slack/types' +import type { ToolConfig } from '@/tools/types' + +export const slackGetChannelInfoTool: ToolConfig< + SlackGetChannelInfoParams, + SlackGetChannelInfoResponse +> = { + id: 'slack_get_channel_info', + name: 'Slack Get Channel Info', + description: 'Get detailed information about a Slack channel by its ID', + version: '1.0.0', + + oauth: { + required: true, + provider: 'slack', + }, + + params: { + authMethod: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication method: oauth or bot_token', + }, + botToken: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Bot token for Custom Bot', + }, + accessToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'OAuth access token or bot token for Slack API', + }, + channel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Channel ID to get information about (e.g., C1234567890)', + }, + includeNumMembers: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include the member count in the response', + }, + }, + + request: { + url: (params: SlackGetChannelInfoParams) => { + const url = new URL('https://slack.com/api/conversations.info') + url.searchParams.append('channel', params.channel.trim()) + url.searchParams.append('include_num_members', String(params.includeNumMembers ?? true)) + return url.toString() + }, + method: 'GET', + headers: (params: SlackGetChannelInfoParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.accessToken || params.botToken}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.ok) { + if (data.error === 'channel_not_found') { + throw new Error('Channel not found. Please check the channel ID and try again.') + } + if (data.error === 'missing_scope') { + throw new Error( + 'Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:read).' + ) + } + throw new Error(data.error || 'Failed to get channel info from Slack') + } + + const channel = data.channel + + return { + success: true, + output: { + channel: { + id: channel.id, + name: channel.name ?? '', + is_channel: channel.is_channel ?? false, + is_private: channel.is_private ?? false, + is_archived: channel.is_archived ?? false, + is_general: channel.is_general ?? false, + is_member: channel.is_member ?? false, + is_shared: channel.is_shared ?? false, + is_ext_shared: channel.is_ext_shared ?? false, + is_org_shared: channel.is_org_shared ?? false, + num_members: channel.num_members ?? null, + topic: channel.topic?.value ?? '', + purpose: channel.purpose?.value ?? '', + created: channel.created ?? null, + creator: channel.creator ?? null, + updated: channel.updated ?? null, + }, + }, + } + }, + + outputs: { + channel: { + type: 'object', + description: 'Detailed channel information', + properties: CHANNEL_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/slack/get_user_presence.ts b/apps/sim/tools/slack/get_user_presence.ts new file mode 100644 index 00000000000..898b62c4ea7 --- /dev/null +++ b/apps/sim/tools/slack/get_user_presence.ts @@ -0,0 +1,117 @@ +import type { SlackGetUserPresenceParams, SlackGetUserPresenceResponse } from '@/tools/slack/types' +import type { ToolConfig } from '@/tools/types' + +export const slackGetUserPresenceTool: ToolConfig< + SlackGetUserPresenceParams, + SlackGetUserPresenceResponse +> = { + id: 'slack_get_user_presence', + name: 'Slack Get User Presence', + description: 'Check whether a Slack user is currently active or away', + version: '1.0.0', + + oauth: { + required: true, + provider: 'slack', + }, + + params: { + authMethod: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Authentication method: oauth or bot_token', + }, + botToken: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Bot token for Custom Bot', + }, + accessToken: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'OAuth access token or bot token for Slack API', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'User ID to check presence for (e.g., U1234567890)', + }, + }, + + request: { + url: (params: SlackGetUserPresenceParams) => { + const url = new URL('https://slack.com/api/users.getPresence') + url.searchParams.append('user', params.userId.trim()) + return url.toString() + }, + method: 'GET', + headers: (params: SlackGetUserPresenceParams) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.accessToken || params.botToken}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!data.ok) { + if (data.error === 'user_not_found') { + throw new Error('User not found. Please check the user ID and try again.') + } + if (data.error === 'missing_scope') { + throw new Error( + 'Missing required permissions. Please reconnect your Slack account with the necessary scopes (users:read).' + ) + } + throw new Error(data.error || 'Failed to get user presence from Slack') + } + + return { + success: true, + output: { + presence: data.presence, + online: data.online ?? null, + autoAway: data.auto_away ?? null, + manualAway: data.manual_away ?? null, + connectionCount: data.connection_count ?? null, + lastActivity: data.last_activity ?? null, + }, + } + }, + + outputs: { + presence: { + type: 'string', + description: 'User presence status: "active" or "away"', + }, + online: { + type: 'boolean', + description: 'Whether user has an active client connection', + optional: true, + }, + autoAway: { + type: 'boolean', + description: 'Whether user was automatically set to away due to inactivity', + optional: true, + }, + manualAway: { + type: 'boolean', + description: 'Whether user manually set themselves as away', + optional: true, + }, + connectionCount: { + type: 'number', + description: 'Total number of active connections for the user', + optional: true, + }, + lastActivity: { + type: 'number', + description: 'Unix timestamp of last detected activity', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/slack/index.ts b/apps/sim/tools/slack/index.ts index 7b93d7c06bb..32aba584c3f 100644 --- a/apps/sim/tools/slack/index.ts +++ b/apps/sim/tools/slack/index.ts @@ -1,11 +1,15 @@ import { slackAddReactionTool } from '@/tools/slack/add_reaction' import { slackCanvasTool } from '@/tools/slack/canvas' +import { slackCreateChannelCanvasTool } from '@/tools/slack/create_channel_canvas' import { slackDeleteMessageTool } from '@/tools/slack/delete_message' import { slackDownloadTool } from '@/tools/slack/download' +import { slackEditCanvasTool } from '@/tools/slack/edit_canvas' import { slackEphemeralMessageTool } from '@/tools/slack/ephemeral_message' +import { slackGetChannelInfoTool } from '@/tools/slack/get_channel_info' import { slackGetMessageTool } from '@/tools/slack/get_message' import { slackGetThreadTool } from '@/tools/slack/get_thread' import { slackGetUserTool } from '@/tools/slack/get_user' +import { slackGetUserPresenceTool } from '@/tools/slack/get_user_presence' import { slackListChannelsTool } from '@/tools/slack/list_channels' import { slackListMembersTool } from '@/tools/slack/list_members' import { slackListUsersTool } from '@/tools/slack/list_users' @@ -17,17 +21,21 @@ import { slackUpdateMessageTool } from '@/tools/slack/update_message' export { slackMessageTool, slackCanvasTool, + slackCreateChannelCanvasTool, slackMessageReaderTool, slackDownloadTool, + slackEditCanvasTool, slackEphemeralMessageTool, slackUpdateMessageTool, slackDeleteMessageTool, slackAddReactionTool, slackRemoveReactionTool, + slackGetChannelInfoTool, slackListChannelsTool, slackListMembersTool, slackListUsersTool, slackGetUserTool, + slackGetUserPresenceTool, slackGetMessageTool, slackGetThreadTool, } diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 754089ccf09..322b253113c 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -606,6 +606,29 @@ export interface SlackGetThreadParams extends SlackBaseParams { limit?: number } +export interface SlackGetChannelInfoParams extends SlackBaseParams { + channel: string + includeNumMembers?: boolean +} + +export interface SlackGetUserPresenceParams extends SlackBaseParams { + userId: string +} + +export interface SlackEditCanvasParams extends SlackBaseParams { + canvasId: string + operation: string + content?: string + sectionId?: string + title?: string +} + +export interface SlackCreateChannelCanvasParams extends SlackBaseParams { + channel: string + title?: string + content?: string +} + export interface SlackMessageResponse extends ToolResponse { output: { // Legacy properties for backward compatibility @@ -875,6 +898,35 @@ export interface SlackGetThreadResponse extends ToolResponse { } } +export interface SlackGetChannelInfoResponse extends ToolResponse { + output: { + channel: SlackChannel + } +} + +export interface SlackGetUserPresenceResponse extends ToolResponse { + output: { + presence: string + online?: boolean | null + autoAway?: boolean | null + manualAway?: boolean | null + connectionCount?: number | null + lastActivity?: number | null + } +} + +export interface SlackEditCanvasResponse extends ToolResponse { + output: { + content: string + } +} + +export interface SlackCreateChannelCanvasResponse extends ToolResponse { + output: { + canvas_id: string + } +} + export type SlackResponse = | SlackCanvasResponse | SlackMessageReaderResponse @@ -891,3 +943,7 @@ export type SlackResponse = | SlackEphemeralMessageResponse | SlackGetMessageResponse | SlackGetThreadResponse + | SlackGetChannelInfoResponse + | SlackGetUserPresenceResponse + | SlackEditCanvasResponse + | SlackCreateChannelCanvasResponse From 3ab4d8dabf280102082713a918abc8719859a99d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 18:59:27 -0800 Subject: [PATCH 2/9] fix(slack): fix download fileName param and canvas error handling --- apps/sim/blocks/blocks/slack.ts | 6 +++--- apps/sim/tools/slack/canvas.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index db05e44ad18..66882ae50b4 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -1025,10 +1025,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, case 'download': { const fileId = (rest as any).fileId - const downloadFileName = (rest as any).downloadFileName + const fileName = (rest as any).fileName baseParams.fileId = fileId - if (downloadFileName) { - baseParams.fileName = downloadFileName + if (fileName) { + baseParams.fileName = fileName } break } diff --git a/apps/sim/tools/slack/canvas.ts b/apps/sim/tools/slack/canvas.ts index 986cf1a8d7c..b1c3dcf8f41 100644 --- a/apps/sim/tools/slack/canvas.ts +++ b/apps/sim/tools/slack/canvas.ts @@ -90,6 +90,15 @@ export const slackCanvasTool: ToolConfig transformResponse: async (response: Response) => { const data = await response.json() + if (!data.ok) { + return { + success: false, + output: { + error: data.error || 'Unknown error', + }, + } + } + return { success: true, output: { From 891b396c215f17f2a0b2b2993f5cc58831344021 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 19:00:03 -0800 Subject: [PATCH 3/9] fix(slack): use markdown format for canvas rename title_content --- apps/sim/tools/slack/edit_canvas.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/slack/edit_canvas.ts b/apps/sim/tools/slack/edit_canvas.ts index 7b6eec5911f..c08382b1c25 100644 --- a/apps/sim/tools/slack/edit_canvas.ts +++ b/apps/sim/tools/slack/edit_canvas.ts @@ -83,8 +83,8 @@ export const slackEditCanvasTool: ToolConfig Date: Wed, 4 Mar 2026 19:03:49 -0800 Subject: [PATCH 4/9] fix(slack): rename channel output to channelInfo and document presence API limitation --- apps/docs/content/docs/en/tools/slack.mdx | 12 ++++++------ apps/sim/blocks/blocks/slack.ts | 22 +++++++++++++++++----- apps/sim/tools/slack/get_channel_info.ts | 4 ++-- apps/sim/tools/slack/get_user_presence.ts | 15 ++++++++++----- apps/sim/tools/slack/types.ts | 2 +- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx index 782cff06301..6f6feb28856 100644 --- a/apps/docs/content/docs/en/tools/slack.mdx +++ b/apps/docs/content/docs/en/tools/slack.mdx @@ -840,7 +840,7 @@ Get detailed information about a Slack channel by its ID | Parameter | Type | Description | | --------- | ---- | ----------- | -| `channel` | object | Detailed channel information | +| `channelInfo` | object | Detailed channel information | | ↳ `id` | string | Channel ID \(e.g., C1234567890\) | | ↳ `name` | string | Channel name without # prefix | | ↳ `is_channel` | boolean | Whether this is a channel | @@ -875,11 +875,11 @@ Check whether a Slack user is currently active or away | Parameter | Type | Description | | --------- | ---- | ----------- | | `presence` | string | User presence status: "active" or "away" | -| `online` | boolean | Whether user has an active client connection | -| `autoAway` | boolean | Whether user was automatically set to away due to inactivity | -| `manualAway` | boolean | Whether user manually set themselves as away | -| `connectionCount` | number | Total number of active connections for the user | -| `lastActivity` | number | Unix timestamp of last detected activity | +| `online` | boolean | Whether user has an active client connection \(only available when checking own presence\) | +| `autoAway` | boolean | Whether user was automatically set to away due to inactivity \(only available when checking own presence\) | +| `manualAway` | boolean | Whether user manually set themselves as away \(only available when checking own presence\) | +| `connectionCount` | number | Total number of active connections for the user \(only available when checking own presence\) | +| `lastActivity` | number | Unix timestamp of last detected activity \(only available when checking own presence\) | ### `slack_edit_canvas` diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 66882ae50b4..009fa0d142d 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -1244,6 +1244,13 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, description: 'Updated message metadata (legacy, use message object instead)', }, + // slack_get_channel_info outputs (get_channel_info operation) + channelInfo: { + type: 'json', + description: + 'Detailed channel object with properties: id, name, is_private, is_archived, is_member, num_members, topic, purpose, created, creator', + }, + // slack_get_user_presence outputs (get_user_presence operation) presence: { type: 'string', @@ -1251,23 +1258,28 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, online: { type: 'boolean', - description: 'Whether user has an active client connection', + description: + 'Whether user has an active client connection (only available when checking own presence)', }, autoAway: { type: 'boolean', - description: 'Whether user was automatically set to away', + description: + 'Whether user was automatically set to away (only available when checking own presence)', }, manualAway: { type: 'boolean', - description: 'Whether user manually set themselves as away', + description: + 'Whether user manually set themselves as away (only available when checking own presence)', }, connectionCount: { type: 'number', - description: 'Total number of active connections', + description: + 'Total number of active connections (only available when checking own presence)', }, lastActivity: { type: 'number', - description: 'Unix timestamp of last detected activity', + description: + 'Unix timestamp of last detected activity (only available when checking own presence)', }, // Trigger outputs (when used as webhook trigger) diff --git a/apps/sim/tools/slack/get_channel_info.ts b/apps/sim/tools/slack/get_channel_info.ts index 5a7485df9c0..1fb5d0906f2 100644 --- a/apps/sim/tools/slack/get_channel_info.ts +++ b/apps/sim/tools/slack/get_channel_info.ts @@ -83,7 +83,7 @@ export const slackGetChannelInfoTool: ToolConfig< return { success: true, output: { - channel: { + channelInfo: { id: channel.id, name: channel.name ?? '', is_channel: channel.is_channel ?? false, @@ -106,7 +106,7 @@ export const slackGetChannelInfoTool: ToolConfig< }, outputs: { - channel: { + channelInfo: { type: 'object', description: 'Detailed channel information', properties: CHANNEL_OUTPUT_PROPERTIES, diff --git a/apps/sim/tools/slack/get_user_presence.ts b/apps/sim/tools/slack/get_user_presence.ts index 898b62c4ea7..de4357050ce 100644 --- a/apps/sim/tools/slack/get_user_presence.ts +++ b/apps/sim/tools/slack/get_user_presence.ts @@ -90,27 +90,32 @@ export const slackGetUserPresenceTool: ToolConfig< }, online: { type: 'boolean', - description: 'Whether user has an active client connection', + description: + 'Whether user has an active client connection (only available when checking own presence)', optional: true, }, autoAway: { type: 'boolean', - description: 'Whether user was automatically set to away due to inactivity', + description: + 'Whether user was automatically set to away due to inactivity (only available when checking own presence)', optional: true, }, manualAway: { type: 'boolean', - description: 'Whether user manually set themselves as away', + description: + 'Whether user manually set themselves as away (only available when checking own presence)', optional: true, }, connectionCount: { type: 'number', - description: 'Total number of active connections for the user', + description: + 'Total number of active connections for the user (only available when checking own presence)', optional: true, }, lastActivity: { type: 'number', - description: 'Unix timestamp of last detected activity', + description: + 'Unix timestamp of last detected activity (only available when checking own presence)', optional: true, }, }, diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 322b253113c..90a36a89ee9 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -900,7 +900,7 @@ export interface SlackGetThreadResponse extends ToolResponse { export interface SlackGetChannelInfoResponse extends ToolResponse { output: { - channel: SlackChannel + channelInfo: SlackChannel } } From 27c0bd97f8563d4460ba5eda70d9656da7a11e9e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 19:04:49 -0800 Subject: [PATCH 5/9] lint --- apps/sim/blocks/blocks/slack.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 009fa0d142d..3b30c75cde7 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -1273,8 +1273,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, connectionCount: { type: 'number', - description: - 'Total number of active connections (only available when checking own presence)', + description: 'Total number of active connections (only available when checking own presence)', }, lastActivity: { type: 'number', From 2470f5756c0c00d04c9567618c5fb933a9ead8a9 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 4 Mar 2026 19:05:45 -0800 Subject: [PATCH 6/9] fix(chat): use explicit trigger type check instead of heuristic for chat guard (#3419) * fix(chat): use explicit trigger type check instead of heuristic for chat guard * fix(chat): remove heuristic fallback from isExecutingFromChat Use only overrideTriggerType === 'chat' instead of also checking for 'input' in workflowInput, which can false-positive on manual executions with workflow input. * fix(chat): use isExecutingFromChat variable consistently in callbacks Replace inline overrideTriggerType !== 'chat' checks with !isExecutingFromChat to stay consistent with the rest of the function. --- .../w/[workflowId]/hooks/use-workflow-execution.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 21f46be3c77..c26c313d6b1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -1124,9 +1124,7 @@ export function useWorkflowExecution() { {} as typeof workflowBlocks ) - const isExecutingFromChat = - overrideTriggerType === 'chat' || - (workflowInput && typeof workflowInput === 'object' && 'input' in workflowInput) + const isExecutingFromChat = overrideTriggerType === 'chat' logger.info('Executing workflow', { isDiffMode: currentWorkflow.isDiffMode, From 1673fc069ba4f2256c44c6daee9800e7aa33bd76 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 19:12:32 -0800 Subject: [PATCH 7/9] fix(slack): add missing fields to SlackChannel interface --- apps/sim/tools/slack/types.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 90a36a89ee9..e41a99af21f 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -802,14 +802,20 @@ export interface SlackRemoveReactionResponse extends ToolResponse { export interface SlackChannel { id: string name: string + is_channel?: boolean is_private: boolean is_archived: boolean + is_general?: boolean is_member: boolean + is_shared?: boolean + is_ext_shared?: boolean + is_org_shared?: boolean num_members?: number topic?: string purpose?: string created?: number creator?: string + updated?: number } export interface SlackListChannelsResponse extends ToolResponse { From 0948986967976d867ccde6a2fd7ba99496fc9dad Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 22:05:29 -0800 Subject: [PATCH 8/9] fix(slack): fix canvas transformResponse type mismatch Provide required output fields on error path to match SlackCanvasResponse type. Co-Authored-By: Claude Opus 4.6 --- apps/sim/tools/slack/canvas.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/slack/canvas.ts b/apps/sim/tools/slack/canvas.ts index b1c3dcf8f41..f8e514ee75f 100644 --- a/apps/sim/tools/slack/canvas.ts +++ b/apps/sim/tools/slack/canvas.ts @@ -87,13 +87,16 @@ export const slackCanvasTool: ToolConfig }, }, - transformResponse: async (response: Response) => { + transformResponse: async (response: Response): Promise => { const data = await response.json() if (!data.ok) { return { success: false, output: { + canvas_id: '', + channel: '', + title: '', error: data.error || 'Unknown error', }, } From 90e83b19ce3ddeea679291e36bf890e152b3fd34 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 4 Mar 2026 22:11:52 -0800 Subject: [PATCH 9/9] fix(slack): move error field to top level in canvas transformResponse The error field belongs on ToolResponse, not inside the output object. Co-Authored-By: Claude Opus 4.6 --- apps/sim/tools/slack/canvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/tools/slack/canvas.ts b/apps/sim/tools/slack/canvas.ts index f8e514ee75f..51fcafb82bf 100644 --- a/apps/sim/tools/slack/canvas.ts +++ b/apps/sim/tools/slack/canvas.ts @@ -97,8 +97,8 @@ export const slackCanvasTool: ToolConfig canvas_id: '', channel: '', title: '', - error: data.error || 'Unknown error', }, + error: data.error || 'Unknown error', } }