Compare commits

...

7 Commits

Author SHA1 Message Date
Waleed Latif
8cbd95404a 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 <noreply@anthropic.com>
2026-03-04 19:27:22 -08:00
Waleed Latif
5e51fa7bf8 fix(slack): add missing fields to SlackChannel interface 2026-03-04 19:23:30 -08:00
Waleed Latif
2ace4b36a1 lint 2026-03-04 19:23:30 -08:00
Waleed Latif
778defc750 fix(slack): rename channel output to channelInfo and document presence API limitation 2026-03-04 19:23:30 -08:00
Waleed Latif
33a9078948 fix(slack): use markdown format for canvas rename title_content 2026-03-04 19:23:30 -08:00
Waleed Latif
78235e6739 fix(slack): fix download fileName param and canvas error handling 2026-03-04 19:23:30 -08:00
Waleed Latif
be18ff4288 feat(slack): add new tools and user selectors 2026-03-04 19:23:30 -08:00
10 changed files with 947 additions and 9 deletions

View File

@@ -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 |
| --------- | ---- | ----------- |
| `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 |
| ↳ `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 \(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`
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 |

View File

@@ -9,7 +9,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
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<SlackResponse> = {
{ 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<SlackResponse> = {
}
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<SlackResponse> = {
}
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<SlackResponse> = {
{
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
@@ -824,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
}
@@ -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,43 @@ 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',
description: 'User presence status: "active" or "away"',
},
online: {
type: 'boolean',
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 (only available when checking own presence)',
},
manualAway: {
type: 'boolean',
description:
'Whether user manually set themselves as away (only available when checking own presence)',
},
connectionCount: {
type: 'number',
description: 'Total number of active connections (only available when checking own presence)',
},
lastActivity: {
type: 'number',
description:
'Unix timestamp of last detected activity (only available when checking own presence)',
},
// 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' },

View File

@@ -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<string, ToolConfig> = {
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,

View File

@@ -87,9 +87,21 @@ export const slackCanvasTool: ToolConfig<SlackCanvasParams, SlackCanvasResponse>
},
},
transformResponse: async (response: Response) => {
transformResponse: async (response: Response): Promise<SlackCanvasResponse> => {
const data = await response.json()
if (!data.ok) {
return {
success: false,
output: {
canvas_id: '',
channel: '',
title: '',
error: data.error || 'Unknown error',
},
}
}
return {
success: true,
output: {

View File

@@ -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<string, unknown> = {
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' },
},
}

View File

@@ -0,0 +1,121 @@
import type { SlackEditCanvasParams, SlackEditCanvasResponse } from '@/tools/slack/types'
import type { ToolConfig } from '@/tools/types'
export const slackEditCanvasTool: ToolConfig<SlackEditCanvasParams, SlackEditCanvasResponse> = {
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<string, unknown> = {
operation: params.operation,
}
if (params.sectionId) {
change.section_id = params.sectionId.trim()
}
if (params.operation === 'rename' && params.title) {
change.title_content = {
type: 'markdown',
markdown: 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' },
},
}

View File

@@ -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: {
channelInfo: {
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: {
channelInfo: {
type: 'object',
description: 'Detailed channel information',
properties: CHANNEL_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -0,0 +1,122 @@
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 (only available when checking own presence)',
optional: true,
},
autoAway: {
type: 'boolean',
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 (only available when checking own presence)',
optional: true,
},
connectionCount: {
type: 'number',
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 (only available when checking own presence)',
optional: true,
},
},
}

View File

@@ -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,
}

View File

@@ -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
@@ -779,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 {
@@ -875,6 +904,35 @@ export interface SlackGetThreadResponse extends ToolResponse {
}
}
export interface SlackGetChannelInfoResponse extends ToolResponse {
output: {
channelInfo: 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 +949,7 @@ export type SlackResponse =
| SlackEphemeralMessageResponse
| SlackGetMessageResponse
| SlackGetThreadResponse
| SlackGetChannelInfoResponse
| SlackGetUserPresenceResponse
| SlackEditCanvasResponse
| SlackCreateChannelCanvasResponse