Skip to content

Commit 22347fe

Browse files
committed
feat(slack): added ephemeral message send tool, updated ci, updated docs
1 parent 1b8d666 commit 22347fe

File tree

11 files changed

+314
-28
lines changed

11 files changed

+314
-28
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ jobs:
144144
tags: ${{ steps.meta.outputs.tags }}
145145
provenance: false
146146
sbom: false
147-
no-cache: true
148147

149148
# Build ARM64 images for GHCR (main branch only, runs in parallel)
150149
build-ghcr-arm64:
@@ -205,7 +204,6 @@ jobs:
205204
tags: ${{ steps.meta.outputs.tags }}
206205
provenance: false
207206
sbom: false
208-
no-cache: true
209207

210208
# Create GHCR multi-arch manifests (only for main, after both builds)
211209
create-ghcr-manifests:

.github/workflows/images.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ jobs:
9797
tags: ${{ steps.meta.outputs.tags }}
9898
provenance: false
9999
sbom: false
100-
no-cache: true
101100

102101
build-ghcr-arm64:
103102
name: Build ARM64 (GHCR Only)
@@ -144,11 +143,10 @@ jobs:
144143
tags: ${{ steps.meta.outputs.tags }}
145144
provenance: false
146145
sbom: false
147-
no-cache: true
148146

149147
create-ghcr-manifests:
150148
name: Create GHCR Manifests
151-
runs-on: blacksmith-8vcpu-ubuntu-2404
149+
runs-on: blacksmith-2vcpu-ubuntu-2404
152150
needs: [build-amd64, build-ghcr-arm64]
153151
if: github.ref == 'refs/heads/main'
154152
strategy:

.github/workflows/test-build.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ jobs:
3838
key: ${{ github.repository }}-node-modules
3939
path: ./node_modules
4040

41+
- name: Mount Next.js cache (Sticky Disk)
42+
uses: useblacksmith/stickydisk@v1
43+
with:
44+
key: ${{ github.repository }}-next-cache
45+
path: ./apps/sim/.next/cache
46+
4147
- name: Install dependencies
4248
run: bun install --frozen-lockfile
4349

apps/docs/content/docs/en/tools/jira_service_management.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Create a new service request in Jira Service Management
116116
| `summary` | string | Yes | Summary/title for the service request |
117117
| `description` | string | No | Description for the service request |
118118
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
119-
| `requestFieldValues` | json | No | Custom field values as key-value pairs \(overrides summary/description if provided\) |
119+
| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) |
120120
| `requestParticipants` | string | No | Comma-separated account IDs to add as request participants |
121121
| `channel` | string | No | Channel the request originates from \(e.g., portal, email\) |
122122

apps/docs/content/docs/en/tools/slack.mdx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Slack
3-
description: Send, update, delete messages, add reactions in Slack or trigger workflows from Slack events
3+
description: Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events
44
---
55

66
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](
5959

6060
## Usage Instructions
6161

62-
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.
62+
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.
6363

6464

6565

@@ -146,6 +146,28 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format
146146
| `fileCount` | number | Number of files uploaded \(when files are attached\) |
147147
| `files` | file[] | Files attached to the message |
148148

149+
### `slack_ephemeral_message`
150+
151+
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.
152+
153+
#### Input
154+
155+
| Parameter | Type | Required | Description |
156+
| --------- | ---- | -------- | ----------- |
157+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
158+
| `botToken` | string | No | Bot token for Custom Bot |
159+
| `channel` | string | Yes | Slack channel ID \(e.g., C1234567890\) |
160+
| `user` | string | Yes | User ID who will see the ephemeral message \(e.g., U1234567890\). Must be a member of the channel. |
161+
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
162+
| `threadTs` | string | No | Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply. |
163+
164+
#### Output
165+
166+
| Parameter | Type | Description |
167+
| --------- | ---- | ----------- |
168+
| `messageTs` | string | Timestamp of the ephemeral message \(cannot be used with chat.update\) |
169+
| `channel` | string | Channel ID where the ephemeral message was sent |
170+
149171
### `slack_canvas`
150172

151173
Create and share Slack canvases in channels. Canvases are collaborative documents within Slack.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
4+
import { checkInternalAuth } from '@/lib/auth/hybrid'
5+
import { generateRequestId } from '@/lib/core/utils/request'
6+
7+
export const dynamic = 'force-dynamic'
8+
9+
const logger = createLogger('SlackSendEphemeralAPI')
10+
11+
const SlackSendEphemeralSchema = z.object({
12+
accessToken: z.string().min(1, 'Access token is required'),
13+
channel: z.string().min(1, 'Channel ID is required'),
14+
user: z.string().min(1, 'User ID is required'),
15+
text: z.string().min(1, 'Message text is required'),
16+
thread_ts: z.string().optional().nullable(),
17+
})
18+
19+
export async function POST(request: NextRequest) {
20+
const requestId = generateRequestId()
21+
22+
try {
23+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
24+
25+
if (!authResult.success) {
26+
logger.warn(`[${requestId}] Unauthorized Slack ephemeral send attempt: ${authResult.error}`)
27+
return NextResponse.json(
28+
{
29+
success: false,
30+
error: authResult.error || 'Authentication required',
31+
},
32+
{ status: 401 }
33+
)
34+
}
35+
36+
logger.info(
37+
`[${requestId}] Authenticated Slack ephemeral send request via ${authResult.authType}`,
38+
{ userId: authResult.userId }
39+
)
40+
41+
const body = await request.json()
42+
const validatedData = SlackSendEphemeralSchema.parse(body)
43+
44+
logger.info(`[${requestId}] Sending ephemeral message`, {
45+
channel: validatedData.channel,
46+
user: validatedData.user,
47+
threadTs: validatedData.thread_ts ?? undefined,
48+
})
49+
50+
const response = await fetch('https://slack.com/api/chat.postEphemeral', {
51+
method: 'POST',
52+
headers: {
53+
'Content-Type': 'application/json',
54+
Authorization: `Bearer ${validatedData.accessToken}`,
55+
},
56+
body: JSON.stringify({
57+
channel: validatedData.channel,
58+
user: validatedData.user,
59+
text: validatedData.text,
60+
...(validatedData.thread_ts && { thread_ts: validatedData.thread_ts }),
61+
}),
62+
})
63+
64+
const data = await response.json()
65+
66+
if (!data.ok) {
67+
logger.error(`[${requestId}] Slack API error:`, data.error)
68+
return NextResponse.json(
69+
{ success: false, error: data.error || 'Failed to send ephemeral message' },
70+
{ status: 400 }
71+
)
72+
}
73+
74+
logger.info(`[${requestId}] Ephemeral message sent successfully`)
75+
76+
return NextResponse.json({
77+
success: true,
78+
output: {
79+
messageTs: data.message_ts,
80+
channel: validatedData.channel,
81+
},
82+
})
83+
} catch (error) {
84+
logger.error(`[${requestId}] Error sending ephemeral message:`, error)
85+
return NextResponse.json(
86+
{
87+
success: false,
88+
error: error instanceof Error ? error.message : 'Unknown error occurred',
89+
},
90+
{ status: 500 }
91+
)
92+
}
93+
}

apps/sim/blocks/blocks/slack.ts

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
99
type: 'slack',
1010
name: 'Slack',
1111
description:
12-
'Send, update, delete messages, add reactions in Slack or trigger workflows from Slack events',
12+
'Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events',
1313
authMode: AuthMode.OAuth,
1414
longDescription:
15-
'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.',
15+
'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.',
1616
docsLink: 'https://docs.sim.ai/tools/slack',
1717
category: 'tools',
1818
bgColor: '#611f69',
@@ -25,6 +25,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
2525
type: 'dropdown',
2626
options: [
2727
{ label: 'Send Message', id: 'send' },
28+
{ label: 'Send Ephemeral Message', id: 'ephemeral' },
2829
{ label: 'Create Canvas', id: 'canvas' },
2930
{ label: 'Read Messages', id: 'read' },
3031
{ label: 'Get Message', id: 'get_message' },
@@ -116,15 +117,21 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
116117
placeholder: 'Select Slack channel',
117118
mode: 'basic',
118119
dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] },
119-
condition: {
120-
field: 'operation',
121-
value: ['list_channels', 'list_users', 'get_user'],
122-
not: true,
123-
and: {
124-
field: 'destinationType',
125-
value: 'dm',
120+
condition: (values?: Record<string, unknown>) => {
121+
const op = values?.operation as string
122+
if (op === 'ephemeral') {
123+
return { field: 'operation', value: 'ephemeral' }
124+
}
125+
return {
126+
field: 'operation',
127+
value: ['list_channels', 'list_users', 'get_user'],
126128
not: true,
127-
},
129+
and: {
130+
field: 'destinationType',
131+
value: 'dm',
132+
not: true,
133+
},
134+
}
128135
},
129136
required: true,
130137
},
@@ -135,15 +142,21 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
135142
canonicalParamId: 'channel',
136143
placeholder: 'Enter Slack channel ID (e.g., C1234567890)',
137144
mode: 'advanced',
138-
condition: {
139-
field: 'operation',
140-
value: ['list_channels', 'list_users', 'get_user'],
141-
not: true,
142-
and: {
143-
field: 'destinationType',
144-
value: 'dm',
145+
condition: (values?: Record<string, unknown>) => {
146+
const op = values?.operation as string
147+
if (op === 'ephemeral') {
148+
return { field: 'operation', value: 'ephemeral' }
149+
}
150+
return {
151+
field: 'operation',
152+
value: ['list_channels', 'list_users', 'get_user'],
145153
not: true,
146-
},
154+
and: {
155+
field: 'destinationType',
156+
value: 'dm',
157+
not: true,
158+
},
159+
}
147160
},
148161
required: true,
149162
},
@@ -175,14 +188,25 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
175188
},
176189
required: true,
177190
},
191+
{
192+
id: 'ephemeralUser',
193+
title: 'Target User',
194+
type: 'short-input',
195+
placeholder: 'User ID who will see the message (e.g., U1234567890)',
196+
condition: {
197+
field: 'operation',
198+
value: 'ephemeral',
199+
},
200+
required: true,
201+
},
178202
{
179203
id: 'text',
180204
title: 'Message',
181205
type: 'long-input',
182206
placeholder: 'Enter your message (supports Slack mrkdwn)',
183207
condition: {
184208
field: 'operation',
185-
value: 'send',
209+
value: ['send', 'ephemeral'],
186210
},
187211
required: true,
188212
},
@@ -193,7 +217,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
193217
placeholder: 'Reply to thread (e.g., 1405894322.002768)',
194218
condition: {
195219
field: 'operation',
196-
value: 'send',
220+
value: ['send', 'ephemeral'],
197221
},
198222
required: false,
199223
},
@@ -499,6 +523,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
499523
tools: {
500524
access: [
501525
'slack_message',
526+
'slack_ephemeral_message',
502527
'slack_canvas',
503528
'slack_message_reader',
504529
'slack_get_message',
@@ -517,6 +542,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
517542
switch (params.operation) {
518543
case 'send':
519544
return 'slack_message'
545+
case 'ephemeral':
546+
return 'slack_ephemeral_message'
520547
case 'canvas':
521548
return 'slack_canvas'
522549
case 'read':
@@ -561,6 +588,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
561588
oldest,
562589
files,
563590
threadTs,
591+
ephemeralUser,
564592
updateTimestamp,
565593
updateText,
566594
deleteTimestamp,
@@ -614,6 +642,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
614642
break
615643
}
616644

645+
case 'ephemeral': {
646+
baseParams.text = text
647+
baseParams.user = ephemeralUser ? String(ephemeralUser).trim() : ''
648+
if (threadTs) {
649+
baseParams.threadTs = threadTs
650+
}
651+
break
652+
}
653+
617654
case 'canvas':
618655
baseParams.title = title
619656
baseParams.content = content
@@ -731,6 +768,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
731768
// List Users inputs
732769
includeDeleted: { type: 'string', description: 'Include deactivated users (true/false)' },
733770
userLimit: { type: 'string', description: 'Maximum number of users to return' },
771+
// Ephemeral message inputs
772+
ephemeralUser: { type: 'string', description: 'User ID who will see the ephemeral message' },
734773
// Get User inputs
735774
userId: { type: 'string', description: 'User ID to look up' },
736775
// Get Message inputs
@@ -758,6 +797,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
758797
},
759798
files: { type: 'file[]', description: 'Files attached to the message' },
760799

800+
// slack_ephemeral_message outputs (ephemeral operation)
801+
messageTs: {
802+
type: 'string',
803+
description: 'Timestamp of the ephemeral message (cannot be used to update or delete)',
804+
},
805+
761806
// slack_canvas outputs
762807
canvas_id: { type: 'string', description: 'Canvas identifier for created canvases' },
763808
title: { type: 'string', description: 'Canvas title' },

apps/sim/tools/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,7 @@ import {
15411541
slackCanvasTool,
15421542
slackDeleteMessageTool,
15431543
slackDownloadTool,
1544+
slackEphemeralMessageTool,
15441545
slackGetMessageTool,
15451546
slackGetThreadTool,
15461547
slackGetUserTool,
@@ -2216,6 +2217,7 @@ export const tools: Record<string, ToolConfig> = {
22162217
slack_get_thread: slackGetThreadTool,
22172218
slack_canvas: slackCanvasTool,
22182219
slack_download: slackDownloadTool,
2220+
slack_ephemeral_message: slackEphemeralMessageTool,
22192221
slack_update_message: slackUpdateMessageTool,
22202222
slack_delete_message: slackDeleteMessageTool,
22212223
slack_add_reaction: slackAddReactionTool,

0 commit comments

Comments
 (0)