diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
index 7526064411e..9660f2cec21 100644
--- a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx
@@ -310,7 +310,7 @@ function StepConfigure({
{allSelected && (
Full access — the bot can read and send messages, react, upload files, and chat as an AI
- assistant.
+ agent.
)}
diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts
index 1792db96c52..e413d0a12c0 100644
--- a/apps/sim/blocks/blocks/slack.ts
+++ b/apps/sim/blocks/blocks/slack.ts
@@ -21,9 +21,10 @@ export const SlackBlock: BlockConfig = {
bgColor: '#611f69',
icon: SlackIcon,
triggerAllowed: true,
- // Superseded by slack_v2, but stays discoverable until v2 GAs — hiding both
- // would leave no Slack block in the toolbar while v2 is preview-gated. At v2
- // GA this becomes `hideFromToolbar: true` (superseded-version paradigm).
+ // Superseded by slack_v2 (GA): hidden from discovery like other legacy _vN
+ // blocks; existing workflows keep executing it.
+ hideFromToolbar: true,
+ sunset: { status: 'legacy', replacedBy: 'slack_v2' },
subBlocks: [
{
id: 'operation',
@@ -2465,9 +2466,10 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te
team_id: { type: 'string', description: 'Slack workspace/team ID' },
event_id: { type: 'string', description: 'Unique event identifier for the trigger' },
},
- // Trigger capabilities moved to slack_v2 so the trigger surfaces once.
- // Legacy webhook trigger stays available while slack_v2 (which hosts the
- // redesigned slack_oauth trigger) is preview-gated; drops at v2 GA.
+ // The legacy webhook trigger must STAY enabled: webhook-execution gates on
+ // blockConfig.triggers.enabled at runtime, so disabling it would break every
+ // deployed v1 Slack trigger workflow. New discovery surfaces only slack_v2
+ // (this block is hideFromToolbar).
triggers: {
enabled: true,
available: ['slack_webhook'],
@@ -2627,6 +2629,11 @@ export const SlackBlockMeta = {
],
} as const satisfies BlockMeta
+export const SlackV2BlockMeta = {
+ tags: ['messaging', 'webhooks', 'automation'],
+ url: 'https://slack.com',
+} as const satisfies BlockMeta
+
const SLACK_WEBHOOK_TRIGGER_SUBBLOCK_IDS = new Set(
getTrigger('slack_webhook').subBlocks.map((sb) => sb.id)
)
@@ -2677,11 +2684,8 @@ export const SlackV2Block: BlockConfig = {
...SlackBlock,
type: 'slack_v2',
hideFromToolbar: false,
- // Preview-gated: hidden from every discovery surface until revealed via the
- // block-visibility AppConfig (hosted) or PREVIEW_BLOCKS=slack_v2 (dev /
- // self-host). At GA: drop this flag, add SlackV2BlockMeta + docs, and set
- // hideFromToolbar on v1.
- preview: true,
+ // The spread inherits v1's sunset marker — clear it: this IS the GA successor.
+ sunset: undefined,
subBlocks: [
...SlackBlock.subBlocks.flatMap((sb) => {
// Drop the legacy paste-secret trigger config (v1 hosts slack_webhook)
diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts
index d3bc8cea720..1eed1a6f194 100644
--- a/apps/sim/blocks/registry-maps.ts
+++ b/apps/sim/blocks/registry-maps.ts
@@ -278,7 +278,7 @@ import { ShopifyBlock, ShopifyBlockMeta } from '@/blocks/blocks/shopify'
import { SimWorkspaceEventBlock } from '@/blocks/blocks/sim_workspace_event'
import { SimilarwebBlock, SimilarwebBlockMeta } from '@/blocks/blocks/similarweb'
import { SixtyfourBlock, SixtyfourBlockMeta } from '@/blocks/blocks/sixtyfour'
-import { SlackBlock, SlackBlockMeta, SlackV2Block } from '@/blocks/blocks/slack'
+import { SlackBlock, SlackBlockMeta, SlackV2Block, SlackV2BlockMeta } from '@/blocks/blocks/slack'
import { SmtpBlock, SmtpBlockMeta } from '@/blocks/blocks/smtp'
import { SportmonksBlock, SportmonksBlockMeta } from '@/blocks/blocks/sportmonks'
import { SpotifyBlock, SpotifyBlockMeta } from '@/blocks/blocks/spotify'
@@ -878,6 +878,7 @@ export const BLOCK_META_REGISTRY: Record = {
similarweb: SimilarwebBlockMeta,
sixtyfour: SixtyfourBlockMeta,
slack: SlackBlockMeta,
+ slack_v2: SlackV2BlockMeta,
smtp: SmtpBlockMeta,
sportmonks: SportmonksBlockMeta,
spotify: SpotifyBlockMeta,
diff --git a/apps/sim/lib/webhooks/providers/slack.ts b/apps/sim/lib/webhooks/providers/slack.ts
index fcd88767072..a5b02e62791 100644
--- a/apps/sim/lib/webhooks/providers/slack.ts
+++ b/apps/sim/lib/webhooks/providers/slack.ts
@@ -117,6 +117,13 @@ interface SlackTriggerEvent {
* submissions). Null for non-block_actions payloads.
*/
state: Record | null
+ /**
+ * What the user is currently viewing, for `app_context_changed` (agent
+ * messaging experience): `{ entities: [{ type, value, team_id }] }`, ordered
+ * by relevance; an empty object when Slack has no entities. Null for every
+ * other event type.
+ */
+ context: Record | null
hasFiles: boolean
files: SlackDownloadedFile[]
}
@@ -151,6 +158,7 @@ function createSlackEvent(): SlackTriggerEvent {
view: null,
message: null,
state: null,
+ context: null,
hasFiles: false,
files: [],
}
@@ -605,6 +613,7 @@ export function resolveSlackEventKey(body: Record): string | nu
case 'pin_removed':
case 'team_join':
case 'app_home_opened':
+ case 'app_context_changed':
case 'assistant_thread_started':
case 'assistant_thread_context_changed':
return type
@@ -977,6 +986,9 @@ export const slackHandler: WebhookProviderHandler = {
event.message_ts = messageTs
event.hasFiles = hasFiles
event.files = files
+ if (eventType === 'app_context_changed') {
+ event.context = (rawEvent?.context as Record | undefined) ?? {}
+ }
return { input: { event } }
},
diff --git a/apps/sim/triggers/slack/capabilities.test.ts b/apps/sim/triggers/slack/capabilities.test.ts
index be56ce1cd07..9d068d407de 100644
--- a/apps/sim/triggers/slack/capabilities.test.ts
+++ b/apps/sim/triggers/slack/capabilities.test.ts
@@ -31,7 +31,7 @@ describe('buildSlackManifest - interactivity', () => {
})
describe('buildSlackManifest - description', () => {
- it('emits display_information.description and reuses it as the assistant description', () => {
+ it('emits display_information.description and reuses it as the agent description', () => {
const manifest = buildSlackManifest(new Set(['action_assistant']), {
...opts,
description: 'Answers support questions.',
@@ -41,15 +41,29 @@ describe('buildSlackManifest - description', () => {
description: 'Answers support questions.',
})
const features = manifest.features as Record>
- expect(features.assistant_view.assistant_description).toBe('Answers support questions.')
+ expect(features.agent_view.agent_description).toBe('Answers support questions.')
})
- it('omits the description key and falls back for the assistant when absent', () => {
+ it('omits the description key and falls back for the agent when absent', () => {
const manifest = buildSlackManifest(new Set(['action_assistant']), opts)
expect(manifest.display_information).toEqual({ name: 'Test Bot' })
const features = manifest.features as Record>
- expect(features.assistant_view.assistant_description).toBe(
- 'Test Bot — an AI assistant powered by Sim.'
- )
+ expect(features.agent_view.agent_description).toBe('Test Bot — an AI agent powered by Sim.')
+ })
+
+ it('caps the agent description at the 300-char manifest limit', () => {
+ const manifest = buildSlackManifest(new Set(['action_assistant']), {
+ ...opts,
+ description: 'x'.repeat(400),
+ })
+ const features = manifest.features as Record>
+ expect((features.agent_view.agent_description as string).length).toBe(300)
+ })
+
+ it('subscribes the agent-experience events, not the deprecated assistant_thread_* set', () => {
+ const manifest = buildSlackManifest(new Set(['action_assistant']), opts)
+ const settings = settingsOf(manifest)
+ const events = (settings.event_subscriptions as Record).bot_events as string[]
+ expect(events).toEqual(['app_context_changed', 'app_home_opened', 'message.im'])
})
})
diff --git a/apps/sim/triggers/slack/capabilities.ts b/apps/sim/triggers/slack/capabilities.ts
index 26bae846059..50f46961d64 100644
--- a/apps/sim/triggers/slack/capabilities.ts
+++ b/apps/sim/triggers/slack/capabilities.ts
@@ -20,10 +20,12 @@ export interface SlackCapability {
scopes: readonly string[]
events: readonly string[]
/**
- * Marks the AI Assistant capability. When enabled the manifest additionally
- * declares the app as an Agents & AI app (`features.assistant_view`) and
- * enables the App Home messages tab — required for assistant threads, the
- * "thinking" status (`assistant.threads.setStatus`), and DM-style chat to work.
+ * Marks the AI agent capability. When enabled the manifest additionally
+ * declares the app as an Agents & AI app (`features.agent_view` — the current
+ * agent messaging experience; new Slack apps cannot use the deprecated
+ * `assistant_view`) and enables the App Home messages tab — required for the
+ * agent surface, the "thinking" status (`assistant.threads.setStatus`), and
+ * DM-style chat to work.
*/
assistant?: boolean
/**
@@ -166,13 +168,18 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [
},
{
id: 'action_assistant',
- label: 'AI assistant',
+ label: 'AI agent',
description:
- 'Register the bot as an AI assistant: users open an assistant thread, the bot shows a "thinking" status, and can set the thread title and suggested prompts (assistant.threads.*).',
+ 'Register the bot as an AI agent: users chat with it in the agent surface, the bot shows a "thinking" status, and can set the thread title and suggested prompts (assistant.threads.*).',
defaultChecked: true,
group: 'action',
scopes: ['assistant:write', 'im:history'],
- events: ['assistant_thread_started', 'assistant_thread_context_changed', 'message.im'],
+ // The agent messaging experience's event set: app_home_opened (user opened
+ // the agent DM), app_context_changed (what the user is viewing; requires
+ // features.agent_view), message.im (incoming messages). The legacy
+ // assistant_thread_* events belong to the deprecated assistant_view
+ // experience and are not subscribable by new agent_view apps.
+ events: ['app_home_opened', 'app_context_changed', 'message.im'],
assistant: true,
},
{
@@ -211,7 +218,7 @@ const WEBHOOK_URL_PLACEHOLDER = ''
export interface BuildManifestOptions {
appName: string
webhookUrl: string | null
- /** Shown on the bot's Slack profile and as the assistant description. */
+ /** Shown on the bot's Slack profile and as the agent description. */
description?: string
}
@@ -241,12 +248,16 @@ export function buildSlackManifest(
bot_user: { display_name: displayName, always_online: true },
}
if (isAssistant) {
- // Declares the app as an Agents & AI app; without this Slack won't surface
- // the assistant thread UI or fire assistant_thread_* events. The messages
- // tab must be enabled so users can chat the assistant.
- features.assistant_view = {
- assistant_description:
- trimmedDescription || `${displayName} — an AI assistant powered by Sim.`,
+ // Declares the app as an Agents & AI app via the current agent messaging
+ // experience. New Slack apps can ONLY use agent_view (assistant_view is
+ // deprecated and rejected for new apps; switching an existing app to
+ // agent_view is irreversible). Without this Slack won't surface the agent
+ // UI or fire app_context_changed. The messages tab must be enabled so
+ // users can chat with the agent. agent_description caps at 300 chars.
+ features.agent_view = {
+ agent_description: (
+ trimmedDescription || `${displayName} — an AI agent powered by Sim.`
+ ).slice(0, 300),
}
features.app_home = {
home_tab_enabled: false,
diff --git a/apps/sim/triggers/slack/shared.ts b/apps/sim/triggers/slack/shared.ts
index 4372f7ba68b..144cea94a09 100644
--- a/apps/sim/triggers/slack/shared.ts
+++ b/apps/sim/triggers/slack/shared.ts
@@ -127,6 +127,11 @@ export const SLACK_TRIGGER_OUTPUTS: Record = {
description:
'Timestamp of the message the interaction originated from. Present for block_actions',
},
+ context: {
+ type: 'json',
+ description:
+ 'What the user is currently viewing: an object with an entities array (each entry: type, value, team_id), e.g. context.entities[0].value. Present for app_context_changed; null otherwise',
+ },
view: {
type: 'json',
description:
@@ -245,6 +250,12 @@ export const SLACK_EVENT_CATALOG: readonly SlackEventCatalogEntry[] = [
{ id: 'pin_removed', label: 'Pin removed', simSubscribed: false, filters: ['channels'] },
{ id: 'team_join', label: 'Member joined workspace', simSubscribed: false, filters: [] },
{ id: 'app_home_opened', label: 'App home opened', simSubscribed: false, filters: [] },
+ {
+ id: 'app_context_changed',
+ label: 'App context changed',
+ simSubscribed: false,
+ filters: [],
+ },
{ id: 'assistant_thread_started', label: 'Assistant opened', simSubscribed: true, filters: [] },
{
id: 'assistant_thread_context_changed',