From 22347fe2dd5eb8a9ceaed45a3d44be5f800a6f3d Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 20 Feb 2026 15:48:43 -0800 Subject: [PATCH 1/5] feat(slack): added ephemeral message send tool, updated ci, updated docs --- .github/workflows/ci.yml | 2 - .github/workflows/images.yml | 4 +- .github/workflows/test-build.yml | 6 + .../docs/en/tools/jira_service_management.mdx | 2 +- apps/docs/content/docs/en/tools/slack.mdx | 26 ++++- .../api/tools/slack/send-ephemeral/route.ts | 93 ++++++++++++++++ apps/sim/blocks/blocks/slack.ts | 85 ++++++++++---- apps/sim/tools/registry.ts | 2 + apps/sim/tools/slack/ephemeral_message.ts | 105 ++++++++++++++++++ apps/sim/tools/slack/index.ts | 2 + apps/sim/tools/slack/types.ts | 15 +++ 11 files changed, 314 insertions(+), 28 deletions(-) create mode 100644 apps/sim/app/api/tools/slack/send-ephemeral/route.ts create mode 100644 apps/sim/tools/slack/ephemeral_message.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f369210ec0..73e5e852fb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,6 @@ jobs: tags: ${{ steps.meta.outputs.tags }} provenance: false sbom: false - no-cache: true # Build ARM64 images for GHCR (main branch only, runs in parallel) build-ghcr-arm64: @@ -205,7 +204,6 @@ jobs: tags: ${{ steps.meta.outputs.tags }} provenance: false sbom: false - no-cache: true # Create GHCR multi-arch manifests (only for main, after both builds) create-ghcr-manifests: diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index e3136510eb1..44e8636d909 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -97,7 +97,6 @@ jobs: tags: ${{ steps.meta.outputs.tags }} provenance: false sbom: false - no-cache: true build-ghcr-arm64: name: Build ARM64 (GHCR Only) @@ -144,11 +143,10 @@ jobs: tags: ${{ steps.meta.outputs.tags }} provenance: false sbom: false - no-cache: true create-ghcr-manifests: name: Create GHCR Manifests - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: blacksmith-2vcpu-ubuntu-2404 needs: [build-amd64, build-ghcr-arm64] if: github.ref == 'refs/heads/main' strategy: diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 96480e7f2bb..7a7fc2792e5 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -38,6 +38,12 @@ jobs: key: ${{ github.repository }}-node-modules path: ./node_modules + - name: Mount Next.js cache (Sticky Disk) + uses: useblacksmith/stickydisk@v1 + with: + key: ${{ github.repository }}-next-cache + path: ./apps/sim/.next/cache + - name: Install dependencies run: bun install --frozen-lockfile diff --git a/apps/docs/content/docs/en/tools/jira_service_management.mdx b/apps/docs/content/docs/en/tools/jira_service_management.mdx index 9814f81036f..cd294152d3e 100644 --- a/apps/docs/content/docs/en/tools/jira_service_management.mdx +++ b/apps/docs/content/docs/en/tools/jira_service_management.mdx @@ -116,7 +116,7 @@ Create a new service request in Jira Service Management | `summary` | string | Yes | Summary/title for the service request | | `description` | string | No | Description for the service request | | `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of | -| `requestFieldValues` | json | No | Custom field values as key-value pairs \(overrides summary/description if provided\) | +| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) | | `requestParticipants` | string | No | Comma-separated account IDs to add as request participants | | `channel` | string | No | Channel the request originates from \(e.g., portal, email\) | diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx index 0f4285e2a16..73edd6b4388 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, add reactions in Slack or trigger workflows from Slack events +description: Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -59,7 +59,7 @@ If you encounter issues with the Slack integration, contact us at [help@sim.ai]( ## Usage Instructions -Integrate Slack into the workflow. Can send, update, and delete messages, create canvases, read messages, and add 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. +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 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. @@ -146,6 +146,28 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format | `fileCount` | number | Number of files uploaded \(when files are attached\) | | `files` | file[] | Files attached to the message | +### `slack_ephemeral_message` + +Send an ephemeral message visible only to a specific user in a channel. Optionally reply in a thread. The message does not persist across sessions. + +#### 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 | Slack channel ID \(e.g., C1234567890\) | +| `user` | string | Yes | User ID who will see the ephemeral message \(e.g., U1234567890\). Must be a member of the channel. | +| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) | +| `threadTs` | string | No | Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messageTs` | string | Timestamp of the ephemeral message \(cannot be used with chat.update\) | +| `channel` | string | Channel ID where the ephemeral message was sent | + ### `slack_canvas` Create and share Slack canvases in channels. Canvases are collaborative documents within Slack. diff --git a/apps/sim/app/api/tools/slack/send-ephemeral/route.ts b/apps/sim/app/api/tools/slack/send-ephemeral/route.ts new file mode 100644 index 00000000000..00ca2c9e325 --- /dev/null +++ b/apps/sim/app/api/tools/slack/send-ephemeral/route.ts @@ -0,0 +1,93 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('SlackSendEphemeralAPI') + +const SlackSendEphemeralSchema = z.object({ + accessToken: z.string().min(1, 'Access token is required'), + channel: z.string().min(1, 'Channel ID is required'), + user: z.string().min(1, 'User ID is required'), + text: z.string().min(1, 'Message text is required'), + thread_ts: z.string().optional().nullable(), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Slack ephemeral send attempt: ${authResult.error}`) + return NextResponse.json( + { + success: false, + error: authResult.error || 'Authentication required', + }, + { status: 401 } + ) + } + + logger.info( + `[${requestId}] Authenticated Slack ephemeral send request via ${authResult.authType}`, + { userId: authResult.userId } + ) + + const body = await request.json() + const validatedData = SlackSendEphemeralSchema.parse(body) + + logger.info(`[${requestId}] Sending ephemeral message`, { + channel: validatedData.channel, + user: validatedData.user, + threadTs: validatedData.thread_ts ?? undefined, + }) + + const response = await fetch('https://slack.com/api/chat.postEphemeral', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${validatedData.accessToken}`, + }, + body: JSON.stringify({ + channel: validatedData.channel, + user: validatedData.user, + text: validatedData.text, + ...(validatedData.thread_ts && { thread_ts: validatedData.thread_ts }), + }), + }) + + const data = await response.json() + + if (!data.ok) { + logger.error(`[${requestId}] Slack API error:`, data.error) + return NextResponse.json( + { success: false, error: data.error || 'Failed to send ephemeral message' }, + { status: 400 } + ) + } + + logger.info(`[${requestId}] Ephemeral message sent successfully`) + + return NextResponse.json({ + success: true, + output: { + messageTs: data.message_ts, + channel: validatedData.channel, + }, + }) + } catch (error) { + logger.error(`[${requestId}] Error sending ephemeral message:`, error) + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred', + }, + { status: 500 } + ) + } +} diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index c4337fab4c8..7cf4b2c944f 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -9,10 +9,10 @@ export const SlackBlock: BlockConfig = { type: 'slack', name: 'Slack', description: - 'Send, update, delete messages, add reactions in Slack or trigger workflows from Slack events', + 'Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events', authMode: AuthMode.OAuth, longDescription: - 'Integrate Slack into the workflow. Can send, update, and delete messages, create canvases, read messages, and add 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.', + '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 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.', docsLink: 'https://docs.sim.ai/tools/slack', category: 'tools', bgColor: '#611f69', @@ -25,6 +25,7 @@ export const SlackBlock: BlockConfig = { type: 'dropdown', options: [ { label: 'Send Message', id: 'send' }, + { label: 'Send Ephemeral Message', id: 'ephemeral' }, { label: 'Create Canvas', id: 'canvas' }, { label: 'Read Messages', id: 'read' }, { label: 'Get Message', id: 'get_message' }, @@ -116,15 +117,21 @@ export const SlackBlock: BlockConfig = { placeholder: 'Select Slack channel', mode: 'basic', dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, - condition: { - field: 'operation', - value: ['list_channels', 'list_users', 'get_user'], - not: true, - and: { - field: 'destinationType', - value: 'dm', + condition: (values?: Record) => { + const op = values?.operation as string + if (op === 'ephemeral') { + return { field: 'operation', value: 'ephemeral' } + } + return { + field: 'operation', + value: ['list_channels', 'list_users', 'get_user'], not: true, - }, + and: { + field: 'destinationType', + value: 'dm', + not: true, + }, + } }, required: true, }, @@ -135,15 +142,21 @@ export const SlackBlock: BlockConfig = { canonicalParamId: 'channel', placeholder: 'Enter Slack channel ID (e.g., C1234567890)', mode: 'advanced', - condition: { - field: 'operation', - value: ['list_channels', 'list_users', 'get_user'], - not: true, - and: { - field: 'destinationType', - value: 'dm', + condition: (values?: Record) => { + const op = values?.operation as string + if (op === 'ephemeral') { + return { field: 'operation', value: 'ephemeral' } + } + return { + field: 'operation', + value: ['list_channels', 'list_users', 'get_user'], not: true, - }, + and: { + field: 'destinationType', + value: 'dm', + not: true, + }, + } }, required: true, }, @@ -175,6 +188,17 @@ export const SlackBlock: BlockConfig = { }, required: true, }, + { + id: 'ephemeralUser', + title: 'Target User', + type: 'short-input', + placeholder: 'User ID who will see the message (e.g., U1234567890)', + condition: { + field: 'operation', + value: 'ephemeral', + }, + required: true, + }, { id: 'text', title: 'Message', @@ -182,7 +206,7 @@ export const SlackBlock: BlockConfig = { placeholder: 'Enter your message (supports Slack mrkdwn)', condition: { field: 'operation', - value: 'send', + value: ['send', 'ephemeral'], }, required: true, }, @@ -193,7 +217,7 @@ export const SlackBlock: BlockConfig = { placeholder: 'Reply to thread (e.g., 1405894322.002768)', condition: { field: 'operation', - value: 'send', + value: ['send', 'ephemeral'], }, required: false, }, @@ -499,6 +523,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, tools: { access: [ 'slack_message', + 'slack_ephemeral_message', 'slack_canvas', 'slack_message_reader', 'slack_get_message', @@ -517,6 +542,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, switch (params.operation) { case 'send': return 'slack_message' + case 'ephemeral': + return 'slack_ephemeral_message' case 'canvas': return 'slack_canvas' case 'read': @@ -561,6 +588,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, oldest, files, threadTs, + ephemeralUser, updateTimestamp, updateText, deleteTimestamp, @@ -614,6 +642,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, break } + case 'ephemeral': { + baseParams.text = text + baseParams.user = ephemeralUser ? String(ephemeralUser).trim() : '' + if (threadTs) { + baseParams.threadTs = threadTs + } + break + } + case 'canvas': baseParams.title = title baseParams.content = content @@ -731,6 +768,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // List Users inputs includeDeleted: { type: 'string', description: 'Include deactivated users (true/false)' }, userLimit: { type: 'string', description: 'Maximum number of users to return' }, + // Ephemeral message inputs + ephemeralUser: { type: 'string', description: 'User ID who will see the ephemeral message' }, // Get User inputs userId: { type: 'string', description: 'User ID to look up' }, // Get Message inputs @@ -758,6 +797,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, files: { type: 'file[]', description: 'Files attached to the message' }, + // slack_ephemeral_message outputs (ephemeral operation) + messageTs: { + type: 'string', + description: 'Timestamp of the ephemeral message (cannot be used to update or delete)', + }, + // slack_canvas outputs canvas_id: { type: 'string', description: 'Canvas identifier for created canvases' }, title: { type: 'string', description: 'Canvas title' }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 2bf07caeb4d..c206509aca7 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1541,6 +1541,7 @@ import { slackCanvasTool, slackDeleteMessageTool, slackDownloadTool, + slackEphemeralMessageTool, slackGetMessageTool, slackGetThreadTool, slackGetUserTool, @@ -2216,6 +2217,7 @@ export const tools: Record = { slack_get_thread: slackGetThreadTool, slack_canvas: slackCanvasTool, slack_download: slackDownloadTool, + slack_ephemeral_message: slackEphemeralMessageTool, slack_update_message: slackUpdateMessageTool, slack_delete_message: slackDeleteMessageTool, slack_add_reaction: slackAddReactionTool, diff --git a/apps/sim/tools/slack/ephemeral_message.ts b/apps/sim/tools/slack/ephemeral_message.ts new file mode 100644 index 00000000000..5e1600a5e05 --- /dev/null +++ b/apps/sim/tools/slack/ephemeral_message.ts @@ -0,0 +1,105 @@ +import type { + SlackEphemeralMessageParams, + SlackEphemeralMessageResponse, +} from '@/tools/slack/types' +import type { ToolConfig } from '@/tools/types' + +export const slackEphemeralMessageTool: ToolConfig< + SlackEphemeralMessageParams, + SlackEphemeralMessageResponse +> = { + id: 'slack_ephemeral_message', + name: 'Slack Ephemeral Message', + description: + 'Send an ephemeral message visible only to a specific user in a channel. Optionally reply in a thread. The message does not persist across sessions.', + 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: 'Slack channel ID (e.g., C1234567890)', + }, + user: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'User ID who will see the ephemeral message (e.g., U1234567890). Must be a member of the channel.', + }, + text: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Message text to send (supports Slack mrkdwn formatting)', + }, + threadTs: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply.', + }, + }, + + request: { + url: '/api/tools/slack/send-ephemeral', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: SlackEphemeralMessageParams) => ({ + accessToken: params.accessToken || params.botToken, + channel: params.channel, + user: params.user?.trim(), + text: params.text, + thread_ts: params.threadTs || undefined, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!data.success) { + throw new Error(data.error || 'Failed to send ephemeral message') + } + return { + success: true, + output: data.output, + } + }, + + outputs: { + messageTs: { + type: 'string', + description: 'Timestamp of the ephemeral message (cannot be used with chat.update)', + }, + channel: { + type: 'string', + description: 'Channel ID where the ephemeral message was sent', + }, + }, +} diff --git a/apps/sim/tools/slack/index.ts b/apps/sim/tools/slack/index.ts index 2bc0f249ef6..e4beed2a895 100644 --- a/apps/sim/tools/slack/index.ts +++ b/apps/sim/tools/slack/index.ts @@ -2,6 +2,7 @@ import { slackAddReactionTool } from '@/tools/slack/add_reaction' import { slackCanvasTool } from '@/tools/slack/canvas' import { slackDeleteMessageTool } from '@/tools/slack/delete_message' import { slackDownloadTool } from '@/tools/slack/download' +import { slackEphemeralMessageTool } from '@/tools/slack/ephemeral_message' import { slackGetMessageTool } from '@/tools/slack/get_message' import { slackGetThreadTool } from '@/tools/slack/get_thread' import { slackGetUserTool } from '@/tools/slack/get_user' @@ -17,6 +18,7 @@ export { slackCanvasTool, slackMessageReaderTool, slackDownloadTool, + slackEphemeralMessageTool, slackUpdateMessageTool, slackDeleteMessageTool, slackAddReactionTool, diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 73ecccbad11..d45e8f11c71 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -584,6 +584,13 @@ export interface SlackGetMessageParams extends SlackBaseParams { timestamp: string } +export interface SlackEphemeralMessageParams extends SlackBaseParams { + channel: string + user: string + text: string + threadTs?: string +} + export interface SlackGetThreadParams extends SlackBaseParams { channel: string threadTs: string @@ -831,6 +838,13 @@ export interface SlackGetMessageResponse extends ToolResponse { } } +export interface SlackEphemeralMessageResponse extends ToolResponse { + output: { + messageTs: string + channel: string + } +} + export interface SlackGetThreadResponse extends ToolResponse { output: { parentMessage: SlackMessage @@ -853,5 +867,6 @@ export type SlackResponse = | SlackListMembersResponse | SlackListUsersResponse | SlackGetUserResponse + | SlackEphemeralMessageResponse | SlackGetMessageResponse | SlackGetThreadResponse From 85f56e7db3ad9cd25e96a8dedc0423e84b509907 Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 20 Feb 2026 16:20:45 -0800 Subject: [PATCH 2/5] added block kit support --- .github/workflows/test-build.yml | 8 +-- apps/docs/content/docs/en/tools/slack.mdx | 3 + .../api/tools/slack/send-ephemeral/route.ts | 3 + .../app/api/tools/slack/send-message/route.ts | 2 + .../api/tools/slack/update-message/route.ts | 3 + apps/sim/app/api/tools/slack/utils.ts | 11 ++-- apps/sim/blocks/blocks/slack.ts | 63 +++++++++++++++++-- apps/sim/tools/slack/ephemeral_message.ts | 9 +++ apps/sim/tools/slack/message.ts | 11 ++++ apps/sim/tools/slack/types.ts | 3 + apps/sim/tools/slack/update_message.ts | 9 +++ docker/app.Dockerfile | 2 +- 12 files changed, 110 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 7a7fc2792e5..10dd6f0b012 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -38,12 +38,6 @@ jobs: key: ${{ github.repository }}-node-modules path: ./node_modules - - name: Mount Next.js cache (Sticky Disk) - uses: useblacksmith/stickydisk@v1 - with: - key: ${{ github.repository }}-next-cache - path: ./apps/sim/.next/cache - - name: Install dependencies run: bun install --frozen-lockfile @@ -116,7 +110,7 @@ jobs: RESEND_API_KEY: 'dummy_key_for_ci_only' AWS_REGION: 'us-west-2' ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only - run: bun run build + run: bunx turbo run build --filter=sim - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx index 73edd6b4388..c51fea7ff2c 100644 --- a/apps/docs/content/docs/en/tools/slack.mdx +++ b/apps/docs/content/docs/en/tools/slack.mdx @@ -80,6 +80,7 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format | `dmUserId` | string | No | Slack user ID for direct messages \(e.g., U1234567890\) | | `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) | | `threadTs` | string | No | Thread timestamp to reply to \(creates thread reply\) | +| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. | | `files` | file[] | No | Files to attach to the message | #### Output @@ -160,6 +161,7 @@ Send an ephemeral message visible only to a specific user in a channel. Optional | `user` | string | Yes | User ID who will see the ephemeral message \(e.g., U1234567890\). Must be a member of the channel. | | `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) | | `threadTs` | string | No | Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply. | +| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. | #### Output @@ -704,6 +706,7 @@ Update a message previously sent by the bot in Slack | `channel` | string | Yes | Channel ID where the message was posted \(e.g., C1234567890\) | | `timestamp` | string | Yes | Timestamp of the message to update \(e.g., 1405894322.002768\) | | `text` | string | Yes | New message text \(supports Slack mrkdwn formatting\) | +| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. | #### Output diff --git a/apps/sim/app/api/tools/slack/send-ephemeral/route.ts b/apps/sim/app/api/tools/slack/send-ephemeral/route.ts index 00ca2c9e325..6d443e5039a 100644 --- a/apps/sim/app/api/tools/slack/send-ephemeral/route.ts +++ b/apps/sim/app/api/tools/slack/send-ephemeral/route.ts @@ -14,6 +14,7 @@ const SlackSendEphemeralSchema = z.object({ user: z.string().min(1, 'User ID is required'), text: z.string().min(1, 'Message text is required'), thread_ts: z.string().optional().nullable(), + blocks: z.array(z.record(z.unknown())).optional().nullable(), }) export async function POST(request: NextRequest) { @@ -58,6 +59,8 @@ export async function POST(request: NextRequest) { user: validatedData.user, text: validatedData.text, ...(validatedData.thread_ts && { thread_ts: validatedData.thread_ts }), + ...(validatedData.blocks && + validatedData.blocks.length > 0 && { blocks: validatedData.blocks }), }), }) diff --git a/apps/sim/app/api/tools/slack/send-message/route.ts b/apps/sim/app/api/tools/slack/send-message/route.ts index 21f60faf6c7..a6b8a3db71c 100644 --- a/apps/sim/app/api/tools/slack/send-message/route.ts +++ b/apps/sim/app/api/tools/slack/send-message/route.ts @@ -17,6 +17,7 @@ const SlackSendMessageSchema = z userId: z.string().optional().nullable(), text: z.string().min(1, 'Message text is required'), thread_ts: z.string().optional().nullable(), + blocks: z.array(z.record(z.unknown())).optional().nullable(), files: RawFileInputArraySchema.optional().nullable(), }) .refine((data) => data.channel || data.userId, { @@ -63,6 +64,7 @@ export async function POST(request: NextRequest) { userId: validatedData.userId ?? undefined, text: validatedData.text, threadTs: validatedData.thread_ts ?? undefined, + blocks: validatedData.blocks ?? undefined, files: validatedData.files ?? undefined, }, requestId, diff --git a/apps/sim/app/api/tools/slack/update-message/route.ts b/apps/sim/app/api/tools/slack/update-message/route.ts index 4edd983a565..ccf0a045294 100644 --- a/apps/sim/app/api/tools/slack/update-message/route.ts +++ b/apps/sim/app/api/tools/slack/update-message/route.ts @@ -13,6 +13,7 @@ const SlackUpdateMessageSchema = z.object({ channel: z.string().min(1, 'Channel is required'), timestamp: z.string().min(1, 'Message timestamp is required'), text: z.string().min(1, 'Message text is required'), + blocks: z.array(z.record(z.unknown())).optional().nullable(), }) export async function POST(request: NextRequest) { @@ -57,6 +58,8 @@ export async function POST(request: NextRequest) { channel: validatedData.channel, ts: validatedData.timestamp, text: validatedData.text, + ...(validatedData.blocks && + validatedData.blocks.length > 0 && { blocks: validatedData.blocks }), }), }) diff --git a/apps/sim/app/api/tools/slack/utils.ts b/apps/sim/app/api/tools/slack/utils.ts index b635c49d8fe..4049a3fe0db 100644 --- a/apps/sim/app/api/tools/slack/utils.ts +++ b/apps/sim/app/api/tools/slack/utils.ts @@ -11,7 +11,8 @@ export async function postSlackMessage( accessToken: string, channel: string, text: string, - threadTs?: string | null + threadTs?: string | null, + blocks?: unknown[] | null ): Promise<{ ok: boolean; ts?: string; channel?: string; message?: any; error?: string }> { const response = await fetch('https://slack.com/api/chat.postMessage', { method: 'POST', @@ -23,6 +24,7 @@ export async function postSlackMessage( channel, text, ...(threadTs && { thread_ts: threadTs }), + ...(blocks && blocks.length > 0 && { blocks }), }), }) @@ -220,6 +222,7 @@ export interface SlackMessageParams { userId?: string text: string threadTs?: string | null + blocks?: unknown[] | null files?: any[] | null } @@ -242,7 +245,7 @@ export async function sendSlackMessage( } error?: string }> { - const { accessToken, text, threadTs, files } = params + const { accessToken, text, threadTs, blocks, files } = params let { channel } = params if (!channel && params.userId) { @@ -258,7 +261,7 @@ export async function sendSlackMessage( if (!files || files.length === 0) { logger.info(`[${requestId}] No files, using chat.postMessage`) - const data = await postSlackMessage(accessToken, channel, text, threadTs) + const data = await postSlackMessage(accessToken, channel, text, threadTs, blocks) if (!data.ok) { logger.error(`[${requestId}] Slack API error:`, data.error) @@ -282,7 +285,7 @@ export async function sendSlackMessage( if (fileIds.length === 0) { logger.warn(`[${requestId}] No valid files to upload, sending text-only message`) - const data = await postSlackMessage(accessToken, channel, text, threadTs) + const data = await postSlackMessage(accessToken, channel, text, threadTs, blocks) if (!data.ok) { return { success: false, error: data.error || 'Failed to send message' } diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 7cf4b2c944f..eaa8e2d369d 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -199,6 +199,20 @@ export const SlackBlock: BlockConfig = { }, required: true, }, + { + id: 'messageFormat', + title: 'Message Format', + type: 'dropdown', + options: [ + { label: 'Plain Text', id: 'text' }, + { label: 'Block Kit', id: 'blocks' }, + ], + value: () => 'text', + condition: { + field: 'operation', + value: ['send', 'ephemeral', 'update'], + }, + }, { id: 'text', title: 'Message', @@ -207,8 +221,29 @@ export const SlackBlock: BlockConfig = { condition: { field: 'operation', value: ['send', 'ephemeral'], + and: { field: 'messageFormat', value: 'blocks', not: true }, + }, + required: { + field: 'operation', + value: ['send', 'ephemeral'], + and: { field: 'messageFormat', value: 'blocks', not: true }, + }, + }, + { + id: 'blocks', + title: 'Block Kit Blocks', + type: 'code', + placeholder: 'JSON array of Block Kit blocks', + condition: { + field: 'operation', + value: ['send', 'ephemeral', 'update'], + and: { field: 'messageFormat', value: 'blocks' }, + }, + required: { + field: 'operation', + value: ['send', 'ephemeral', 'update'], + and: { field: 'messageFormat', value: 'blocks' }, }, - required: true, }, { id: 'threadTs', @@ -480,8 +515,13 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, condition: { field: 'operation', value: 'update', + and: { field: 'messageFormat', value: 'blocks', not: true }, + }, + required: { + field: 'operation', + value: 'update', + and: { field: 'messageFormat', value: 'blocks', not: true }, }, - required: true, }, // Delete Message specific fields { @@ -581,12 +621,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, destinationType, channel, dmUserId, + messageFormat, text, title, content, limit, oldest, files, + blocks, threadTs, ephemeralUser, updateTimestamp, @@ -630,10 +672,13 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, switch (operation) { case 'send': { - baseParams.text = text + baseParams.text = messageFormat === 'blocks' && !text ? ' ' : text if (threadTs) { baseParams.threadTs = threadTs } + if (blocks) { + baseParams.blocks = blocks + } // files is the canonical param from attachmentFiles (basic) or files (advanced) const normalizedFiles = normalizeFileInput(files) if (normalizedFiles) { @@ -643,11 +688,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } case 'ephemeral': { - baseParams.text = text + baseParams.text = messageFormat === 'blocks' && !text ? ' ' : text baseParams.user = ephemeralUser ? String(ephemeralUser).trim() : '' if (threadTs) { baseParams.threadTs = threadTs } + if (blocks) { + baseParams.blocks = blocks + } break } @@ -717,7 +765,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, case 'update': baseParams.timestamp = updateTimestamp - baseParams.text = updateText + baseParams.text = messageFormat === 'blocks' && !updateText ? ' ' : updateText + if (blocks) { + baseParams.blocks = blocks + } break case 'delete': @@ -736,6 +787,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, }, inputs: { operation: { type: 'string', description: 'Operation to perform' }, + messageFormat: { type: 'string', description: 'Message format: text or blocks' }, authMethod: { type: 'string', description: 'Authentication method' }, destinationType: { type: 'string', description: 'Destination type (channel or dm)' }, credential: { type: 'string', description: 'Slack access token' }, @@ -770,6 +822,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, userLimit: { type: 'string', description: 'Maximum number of users to return' }, // Ephemeral message inputs ephemeralUser: { type: 'string', description: 'User ID who will see the ephemeral message' }, + blocks: { type: 'json', description: 'Block Kit layout blocks as a JSON array' }, // Get User inputs userId: { type: 'string', description: 'User ID to look up' }, // Get Message inputs diff --git a/apps/sim/tools/slack/ephemeral_message.ts b/apps/sim/tools/slack/ephemeral_message.ts index 5e1600a5e05..7f5a6c7d40c 100644 --- a/apps/sim/tools/slack/ephemeral_message.ts +++ b/apps/sim/tools/slack/ephemeral_message.ts @@ -64,6 +64,13 @@ export const slackEphemeralMessageTool: ToolConfig< description: 'Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply.', }, + blocks: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text.', + }, }, request: { @@ -78,6 +85,8 @@ export const slackEphemeralMessageTool: ToolConfig< user: params.user?.trim(), text: params.text, thread_ts: params.threadTs || undefined, + blocks: + typeof params.blocks === 'string' ? JSON.parse(params.blocks) : params.blocks || undefined, }), }, diff --git a/apps/sim/tools/slack/message.ts b/apps/sim/tools/slack/message.ts index b1f04740338..5f0a3b31b5a 100644 --- a/apps/sim/tools/slack/message.ts +++ b/apps/sim/tools/slack/message.ts @@ -63,6 +63,13 @@ export const slackMessageTool: ToolConfig Date: Fri, 20 Feb 2026 16:21:30 -0800 Subject: [PATCH 3/5] upgrade turborepo --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 8a73392ae27..848371da3c4 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://v2-8-0.turborepo.dev/schema.json", + "$schema": "https://v2-8-10.turborepo.dev/schema.json", "envMode": "loose", "tasks": { "build": { From b8af0da94c8a50a30a131f87f26886f9d7e4df02 Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 20 Feb 2026 16:23:43 -0800 Subject: [PATCH 4/5] added wandConfig for slack block kit --- apps/sim/blocks/blocks/slack.ts | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index eaa8e2d369d..f1bb5713ce0 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -233,6 +233,7 @@ export const SlackBlock: BlockConfig = { id: 'blocks', title: 'Block Kit Blocks', type: 'code', + language: 'json', placeholder: 'JSON array of Block Kit blocks', condition: { field: 'operation', @@ -244,6 +245,54 @@ export const SlackBlock: BlockConfig = { value: ['send', 'ephemeral', 'update'], and: { field: 'messageFormat', value: 'blocks' }, }, + generationType: 'json-object', + wandConfig: { + enabled: true, + maintainHistory: true, + generationType: 'json-object', + prompt: `You are an expert at Slack Block Kit. +Generate ONLY a valid JSON array of Block Kit blocks based on the user's request. +The output MUST be a JSON array starting with [ and ending with ]. + +Current blocks: {context} + +Available block types for messages: +- "section": Displays text with an optional accessory element. Text uses { "type": "mrkdwn", "text": "..." } or { "type": "plain_text", "text": "..." }. +- "header": Large text header. Text must be plain_text. +- "divider": A horizontal rule separator. No fields needed besides type. +- "image": Displays an image. Requires "image_url" and "alt_text". +- "context": Contextual info with an "elements" array of image and text objects. +- "actions": Interactive elements like buttons. Each button needs "type": "button", a "text" object, and an "action_id". +- "rich_text": Structured rich text with "elements" array of rich_text_section objects. + +Example output: +[ + { + "type": "header", + "text": { "type": "plain_text", "text": "Order Confirmation" } + }, + { + "type": "section", + "text": { "type": "mrkdwn", "text": "Your order *#1234* has been confirmed." } + }, + { "type": "divider" }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { "type": "plain_text", "text": "View Order" }, + "action_id": "view_order", + "url": "https://example.com/orders/1234" + } + ] + } +] + +You can reference workflow variables using angle brackets, e.g., . +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe the Block Kit layout you want to create...', + }, }, { id: 'threadTs', From 08643c4d5e790e65067f04876fe3a676d97bdccf Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 20 Feb 2026 16:30:53 -0800 Subject: [PATCH 5/5] fix generation type --- apps/sim/blocks/blocks/slack.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index f1bb5713ce0..77c44a21bff 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -245,11 +245,9 @@ export const SlackBlock: BlockConfig = { value: ['send', 'ephemeral', 'update'], and: { field: 'messageFormat', value: 'blocks' }, }, - generationType: 'json-object', wandConfig: { enabled: true, maintainHistory: true, - generationType: 'json-object', prompt: `You are an expert at Slack Block Kit. Generate ONLY a valid JSON array of Block Kit blocks based on the user's request. The output MUST be a JSON array starting with [ and ending with ].