Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment thread
waleedlatif1 marked this conversation as resolved.
Outdated
- name: Install dependencies
run: bun install --frozen-lockfile

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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\) |

Expand Down
26 changes: 24 additions & 2 deletions apps/docs/content/docs/en/tools/slack.mdx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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.



Expand Down Expand Up @@ -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.
Expand Down
93 changes: 93 additions & 0 deletions apps/sim/app/api/tools/slack/send-ephemeral/route.ts
Original file line number Diff line number Diff line change
@@ -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 }
)
}
}
85 changes: 65 additions & 20 deletions apps/sim/blocks/blocks/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
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',
Expand All @@ -25,6 +25,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
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' },
Expand Down Expand Up @@ -116,15 +117,21 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
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<string, unknown>) => {
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,
},
Expand All @@ -135,15 +142,21 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
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<string, unknown>) => {
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,
},
Expand Down Expand Up @@ -175,14 +188,25 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
},
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',
type: 'long-input',
placeholder: 'Enter your message (supports Slack mrkdwn)',
condition: {
field: 'operation',
value: 'send',
value: ['send', 'ephemeral'],
},
required: true,
},
Expand All @@ -193,7 +217,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
placeholder: 'Reply to thread (e.g., 1405894322.002768)',
condition: {
field: 'operation',
value: 'send',
value: ['send', 'ephemeral'],
},
required: false,
},
Expand Down Expand Up @@ -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',
Expand All @@ -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':
Expand Down Expand Up @@ -561,6 +588,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
oldest,
files,
threadTs,
ephemeralUser,
updateTimestamp,
updateText,
deleteTimestamp,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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' },
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/tools/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1541,6 +1541,7 @@ import {
slackCanvasTool,
slackDeleteMessageTool,
slackDownloadTool,
slackEphemeralMessageTool,
slackGetMessageTool,
slackGetThreadTool,
slackGetUserTool,
Expand Down Expand Up @@ -2216,6 +2217,7 @@ export const tools: Record<string, ToolConfig> = {
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,
Expand Down
Loading
Loading