diff --git a/apps/docs/content/docs/integrations/slack.mdx b/apps/docs/content/docs/integrations/slack.mdx index 3bb636602ae..d237bca88fd 100644 --- a/apps/docs/content/docs/integrations/slack.mdx +++ b/apps/docs/content/docs/integrations/slack.mdx @@ -816,6 +816,51 @@ Get a stable permalink URL to a specific Slack message. | `channel` | string | Channel ID containing the message | | `permalink` | string | The permalink URL to the message | +### Slack Set Assistant Status + +Set or clear the assistant thread status indicator (the loading shimmer) on a Slack AI app thread. Pass an empty status to clear it. + +#### 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 containing the assistant thread \(e.g., C1234567890 or D1234567890\) | +| `threadTs` | string | Yes | Thread timestamp \(thread_ts\) of the assistant thread \(e.g., 1405894322.002768\) | +| `status` | string | No | Status text to display, e.g. 'Working on it…'. Omit or pass an empty string to clear the status. | +| `loadingMessages` | json | No | Optional list of messages to rotate through as an animated loading indicator \(max 10\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `ok` | boolean | Whether the status was set successfully | +| `channel` | string | Channel ID the status was set on | +| `threadTs` | string | Thread timestamp the status was set on | + +### Slack Set Assistant Title + +Set the title of a Slack assistant thread (shown in the AI app thread header). + +#### 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 containing the assistant thread \(e.g., C1234567890 or D1234567890\) | +| `threadTs` | string | Yes | Thread timestamp \(thread_ts\) of the assistant thread \(e.g., 1405894322.002768\) | +| `title` | string | Yes | The title to display for the assistant thread | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `ok` | boolean | Whether the title was set successfully | +| `channel` | string | Channel ID the title was set on | +| `threadTs` | string | Thread timestamp the title was set on | + ### Slack Set Suggested Prompts Set the clickable suggested prompts shown in a Slack assistant thread (the prompt chips in an AI app). diff --git a/apps/sim/blocks/blocks/slack.test.ts b/apps/sim/blocks/blocks/slack.test.ts index 78fe4d124c0..8cb48edd031 100644 --- a/apps/sim/blocks/blocks/slack.test.ts +++ b/apps/sim/blocks/blocks/slack.test.ts @@ -2,6 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' +import { evaluateSubBlockCondition } from '@/lib/workflows/subblocks/visibility' import { getSlackV2ActionSubBlocks, getSlackV2OperationSentences, @@ -11,7 +12,7 @@ import { } from '@/blocks/blocks/slack' const AGENT_OPERATION_IDS = [ - 'set_suggested_prompts', + 'set_agent_suggested_prompts', 'set_agent_session_status', 'rename_agent_session', ] @@ -20,6 +21,8 @@ const AGENT_TOOL_IDS = [ 'slack_set_agent_session_status_v2', 'slack_rename_agent_session_v2', ] +const ASSISTANT_OPERATION_IDS = ['set_status', 'set_title', 'set_suggested_prompts'] +const ASSISTANT_TOOL_IDS = ['slack_set_status', 'slack_set_title'] function operationIds(): string[] { const operation = SlackV2Block.subBlocks.find((subBlock) => subBlock.id === 'operation') @@ -32,6 +35,12 @@ function mapSlackV2Params(params: Record): Record): boolean { + const subBlock = SlackV2Block.subBlocks.find((candidate) => candidate.id === subBlockId) + if (!subBlock) throw new Error(`Slack v2 subblock not found: ${subBlockId}`) + return evaluateSubBlockCondition(subBlock.condition, values) +} + describe('Slack block release', () => { it('releases slack_v2 and keeps the legacy block executable but hidden', () => { expect(SlackBlock.hideFromToolbar).toBe(true) @@ -41,21 +50,27 @@ describe('Slack block release', () => { expect(SlackV2Block.sunset).toBeUndefined() }) - it('replaces legacy assistant operations with custom-bot Agent Sessions operations', () => { - expect(operationIds()).toEqual(expect.arrayContaining(AGENT_OPERATION_IDS)) - for (const id of ['set_status', 'set_title']) { - expect(operationIds()).not.toContain(id) - } - expect(getSlackV2ToolAccess()).toEqual(expect.arrayContaining(AGENT_TOOL_IDS)) + it('adds custom-bot Agent Sessions operations without removing assistant operations', () => { + expect(operationIds()).toEqual( + expect.arrayContaining([...ASSISTANT_OPERATION_IDS, ...AGENT_OPERATION_IDS]) + ) + expect(getSlackV2ToolAccess()).toEqual( + expect.arrayContaining([...ASSISTANT_TOOL_IDS, ...AGENT_TOOL_IDS]) + ) expect(getSlackV2ToolAccess()).toContain('slack_set_suggested_prompts') - for (const id of ['slack_set_status', 'slack_set_title']) { - expect(getSlackV2ToolAccess()).not.toContain(id) - } expect(Object.keys(getSlackV2OperationSentences())).toEqual( - expect.arrayContaining(AGENT_OPERATION_IDS) + expect.arrayContaining([...ASSISTANT_OPERATION_IDS, ...AGENT_OPERATION_IDS]) ) }) + it('keeps assistant operations routed to their original tools', () => { + const selectTool = SlackV2Block.tools.config?.tool + if (!selectTool) throw new Error('Slack v2 tool selector is required') + + expect(selectTool({ operation: 'set_status' })).toBe('slack_set_status') + expect(selectTool({ operation: 'set_title' })).toBe('slack_set_title') + }) + it('uses a service-account-only picker for Agent Sessions operations', () => { const credential = getSlackV2ActionSubBlocks().find( (subBlock) => subBlock.id === 'agentBotCredential' @@ -67,7 +82,7 @@ describe('Slack block release', () => { }) }) - it('keeps persisted OAuth suggested prompts on the compatibility tool', () => { + it('keeps assistant suggested prompts on the original fields and tool', () => { const selectTool = SlackV2Block.tools.config?.tool if (!selectTool) throw new Error('Slack v2 tool selector is required') @@ -90,6 +105,20 @@ describe('Slack block release', () => { prompts: '[{"title":"Summarize","message":"Summarize this thread"}]', promptsTitle: 'Try asking', }) + + const assistantValues = { + operation: 'set_suggested_prompts', + credential: 'oauth-credential', + channel: 'C123', + getThreadTimestamp: '1700000000.000001', + } + expect(isSlackV2SubBlockVisible('credential', assistantValues)).toBe(true) + expect(isSlackV2SubBlockVisible('channel', assistantValues)).toBe(true) + expect(isSlackV2SubBlockVisible('getThreadTimestamp', assistantValues)).toBe(true) + expect(isSlackV2SubBlockVisible('suggestedPrompts', assistantValues)).toBe(true) + expect(isSlackV2SubBlockVisible('agentBotCredential', assistantValues)).toBe(false) + expect(isSlackV2SubBlockVisible('agentChannel', assistantValues)).toBe(false) + expect(isSlackV2SubBlockVisible('agentThreadTs', assistantValues)).toBe(false) }) it('uses service-account tools for new agent operations', () => { @@ -97,7 +126,7 @@ describe('Slack block release', () => { if (!selectTool) throw new Error('Slack v2 tool selector is required') expect( - selectTool({ operation: 'set_suggested_prompts', agentCredentialId: 'custom-bot' }) + selectTool({ operation: 'set_agent_suggested_prompts', agentCredentialId: 'custom-bot' }) ).toBe('slack_set_suggested_prompts_v2') expect(selectTool({ operation: 'set_agent_session_status' })).toBe( 'slack_set_agent_session_status_v2' @@ -116,5 +145,48 @@ describe('Slack block release', () => { threadTs: '1700000000.000001', status: 'processing', }) + + const agentPromptValues = { + operation: 'set_agent_suggested_prompts', + agentBotCredential: 'custom-bot', + agentChannel: 'D123', + agentThreadTs: '1700000000.000001', + } + expect(isSlackV2SubBlockVisible('credential', agentPromptValues)).toBe(false) + expect(isSlackV2SubBlockVisible('channel', agentPromptValues)).toBe(false) + expect(isSlackV2SubBlockVisible('getThreadTimestamp', agentPromptValues)).toBe(false) + expect(isSlackV2SubBlockVisible('suggestedPrompts', agentPromptValues)).toBe(true) + expect(isSlackV2SubBlockVisible('agentBotCredential', agentPromptValues)).toBe(true) + expect(isSlackV2SubBlockVisible('agentChannel', agentPromptValues)).toBe(true) + expect(isSlackV2SubBlockVisible('agentThreadTs', agentPromptValues)).toBe(true) + + expect( + mapSlackV2Params({ + operation: 'set_agent_suggested_prompts', + agentCredentialId: 'custom-bot', + agentChannelId: 'D123', + agentThreadTs: '1700000000.000001', + suggestedPrompts: '[{"title":"Summarize","message":"Summarize this thread"}]', + promptsTitle: 'Try asking', + }) + ).toMatchObject({ + credential: 'custom-bot', + channel: 'D123', + threadTs: '1700000000.000001', + prompts: '[{"title":"Summarize","message":"Summarize this thread"}]', + promptsTitle: 'Try asking', + }) + + const repurposedValues = { + operation: 'set_agent_suggested_prompts', + credential: 'stale-credential', + channel: 'C123', + suggestedPrompts: '[{"title":"Summarize","message":"Summarize this thread"}]', + } + expect(isSlackV2SubBlockVisible('credential', repurposedValues)).toBe(false) + expect(isSlackV2SubBlockVisible('channel', repurposedValues)).toBe(false) + expect(isSlackV2SubBlockVisible('agentBotCredential', repurposedValues)).toBe(true) + expect(isSlackV2SubBlockVisible('agentChannel', repurposedValues)).toBe(true) + expect(selectTool(repurposedValues)).toBe('slack_set_suggested_prompts_v2') }) }) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 5de1cff9c7c..4fe5904b7a8 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -16,11 +16,13 @@ import { getTrigger } from '@/triggers' const DESTINATION_SWITCH_OPERATIONS = ['send', 'read', 'schedule_message'] as const const SLACK_V2_AGENT_OPERATIONS = [ - 'set_suggested_prompts', + 'set_agent_suggested_prompts', 'set_agent_session_status', 'rename_agent_session', ] as const +const SLACK_V2_SESSION_OPERATIONS = ['set_agent_session_status', 'rename_agent_session'] as const + const CHANNEL_FIELD = ['channel', 'manualChannel'] as const /** @@ -2981,10 +2983,28 @@ function adaptSubBlockForV2(sb: SubBlockConfig): SubBlockConfig { if (sb.id === 'getThreadTimestamp') { return { ...sb, - condition: { field: 'operation', value: ['get_thread', 'get_thread_replies'] }, + condition: { + field: 'operation', + value: [ + 'get_thread', + 'get_thread_replies', + 'set_status', + 'set_title', + 'set_suggested_prompts', + ], + }, required: true, } } + if (sb.id === 'suggestedPrompts' || sb.id === 'promptsTitle') { + return { + ...sb, + condition: { + field: 'operation', + value: ['set_suggested_prompts', 'set_agent_suggested_prompts'], + }, + } + } if (dependsOn && !Array.isArray(dependsOn) && dependsOn.all?.includes('authMethod')) { return { ...sb, dependsOn: ['credential'] } } @@ -3048,10 +3068,7 @@ function getSlackV2AgentSubBlocks(): SubBlockConfig[] { title: 'Thread Timestamp', type: 'short-input', placeholder: 'Thread timestamp (thread_ts)', - condition: { - field: 'operation', - value: ['set_suggested_prompts', 'set_agent_session_status', 'rename_agent_session'], - }, + condition: { field: 'operation', value: [...SLACK_V2_AGENT_OPERATIONS] }, required: { field: 'operation', value: ['set_agent_session_status', 'rename_agent_session'], @@ -3153,12 +3170,11 @@ export function getSlackV2OperationSentences() { if (!operationSentences) { throw new Error('Slack action sentences must be defined before building slack_v2') } - const { set_status: _setStatus, set_title: _setTitle, ...v2Sentences } = operationSentences return { - ...v2Sentences, - set_suggested_prompts: [ + ...operationSentences, + set_agent_suggested_prompts: [ { - text: 'Set suggested prompts in', + text: 'Set agent suggested prompts in', field: ['agentChannel', 'manualAgentChannel'], core: true, }, @@ -3239,7 +3255,10 @@ export const SlackV2Block: BlockConfig = { { label: 'Get Thread Replies', id: 'get_thread_replies' }, { label: 'Get Channel History', id: 'get_channel_history' }, { label: 'Get Message Permalink', id: 'get_permalink' }, - { label: 'Set Suggested Prompts', id: 'set_suggested_prompts' }, + { label: 'Set Assistant Status', id: 'set_status' }, + { label: 'Set Assistant Title', id: 'set_title' }, + { label: 'Set Assistant Suggested Prompts', id: 'set_suggested_prompts' }, + { label: 'Set Agent Suggested Prompts', id: 'set_agent_suggested_prompts' }, { label: 'Set Agent Session Status', id: 'set_agent_session_status' }, { label: 'Rename Agent Session', id: 'rename_agent_session' }, { label: 'List Channels', id: 'list_channels' }, @@ -3290,6 +3309,8 @@ export const SlackV2Block: BlockConfig = { 'slack_get_thread_replies', 'slack_get_channel_history', 'slack_get_permalink', + 'slack_set_status', + 'slack_set_title', 'slack_set_suggested_prompts', 'slack_set_suggested_prompts_v2', 'slack_set_agent_session_status_v2', @@ -3329,9 +3350,9 @@ export const SlackV2Block: BlockConfig = { tool: (params) => { switch (params.operation) { case 'set_suggested_prompts': - return params.agentCredentialId - ? 'slack_set_suggested_prompts_v2' - : 'slack_set_suggested_prompts' + return 'slack_set_suggested_prompts' + case 'set_agent_suggested_prompts': + return 'slack_set_suggested_prompts_v2' case 'set_agent_session_status': return 'slack_set_agent_session_status_v2' case 'rename_agent_session': @@ -3348,9 +3369,6 @@ export const SlackV2Block: BlockConfig = { if (!mapParams) throw new Error('Slack parameter mapper is required') const baseParams = mapParams(params) if (!SLACK_V2_AGENT_OPERATIONS.includes(params.operation as never)) return baseParams - if (params.operation === 'set_suggested_prompts' && !params.agentCredentialId) { - return baseParams - } return { ...baseParams, diff --git a/packages/deployment-config/src/integrations.json b/packages/deployment-config/src/integrations.json index 8913631a032..9126e0de16e 100644 --- a/packages/deployment-config/src/integrations.json +++ b/packages/deployment-config/src/integrations.json @@ -21273,9 +21273,21 @@ "description": "Get a stable permalink URL to a specific Slack message." }, { - "name": "Set Suggested Prompts", + "name": "Set Assistant Status", + "description": "Set or clear the assistant thread status indicator (the loading shimmer) on a Slack AI app thread. Pass an empty status to clear it." + }, + { + "name": "Set Assistant Title", + "description": "Set the title of a Slack assistant thread (shown in the AI app thread header)." + }, + { + "name": "Set Assistant Suggested Prompts", "description": "Set the clickable suggested prompts shown in a Slack assistant thread (the prompt chips in an AI app)." }, + { + "name": "Set Agent Suggested Prompts", + "description": "Set suggested prompts in Slack Agent View, optionally scoped to a specific thread." + }, { "name": "Set Agent Session Status", "description": "Create or update the state of a Slack agent session associated with a thread." @@ -21405,7 +21417,7 @@ "description": "Set the purpose (description) for a Slack channel (max 250 characters)." } ], - "operationCount": 42, + "operationCount": 45, "triggers": [ { "id": "slack_oauth",