@@ -12,6 +12,7 @@ import {
1212 SANDBOX_SELECTABLE_CLI_TOOL_IDS ,
1313} from '@/lib/execution/remote-sandbox/cli-tools'
1414import { type FilterFieldType , getOperatorsForFieldType } from '@/lib/knowledge/filters/types'
15+ import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types'
1516import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils'
1617import { isSubBlockHidden } from '@/lib/workflows/subblocks/visibility'
1718import { getBlock } from '@/blocks'
@@ -24,6 +25,8 @@ import {
2425 SIM_AUTO_MODEL_ID ,
2526} from '@/providers/models'
2627import type { ToolConfig , ToolHostingCondition } from '@/tools/types'
28+ import { buildSlackManifest , SLACK_CAPABILITIES } from '@/triggers/slack/capabilities'
29+ import { buildSlackCustomBotRequestUrl } from '@/triggers/webhook-url'
2730
2831/** The service-account alternative to OAuth for a service, when it offers one. */
2932export interface VfsServiceAccountAuth {
@@ -733,6 +736,15 @@ export function serializeCredentials(
733736 // credential) — they reconnect differently, so the agent must branch on
734737 // this. Env-var credentials carry no type.
735738 type : a . credentialType ,
739+ // Derived, not stored: the public Request URL a Slack custom-bot app
740+ // posts events to. One per credential; every workflow trigger that
741+ // selects this credential shares it. This is what the setup wizard shows
742+ // in Slack's Event Subscriptions step.
743+ ...( a . credentialType === 'service_account' &&
744+ a . providerId === SLACK_CUSTOM_BOT_PROVIDER_ID &&
745+ a . id
746+ ? { requestUrl : buildSlackCustomBotRequestUrl ( a . id ) }
747+ : { } ) ,
736748 connectedAt : a . createdAt . toISOString ( ) ,
737749 } ) ) ,
738750 null ,
@@ -1137,6 +1149,38 @@ export function serializeIntegrationSchema(
11371149 )
11381150}
11391151
1152+ /**
1153+ * Derived setup reference for `slack_oauth` — the same material the custom-bot
1154+ * setup wizard shows, surfaced so the copilot can walk a user (or the browser
1155+ * agent) through Slack app creation without guessing. None of this is a block
1156+ * field: the manifest is a template for api.slack.com, and the Request URL is a
1157+ * per-credential property (`requestUrl` in environment/credentials.json).
1158+ */
1159+ function slackOAuthSetupReference ( ) : Record < string , unknown > {
1160+ const defaults = SLACK_CAPABILITIES . filter ( ( c ) => c . defaultChecked ) . map ( ( c ) => c . id )
1161+ return {
1162+ note :
1163+ 'Setup reference (derived; NOT block fields). A custom bot is a reusable workspace credential: ' +
1164+ 'one Slack app, one Request URL, shared by every trigger that selects it. To create or rotate one, ' +
1165+ 'emit a service_account credential card for provider "slack" — the wizard collects the signing secret ' +
1166+ 'and bot token without them entering the chat. Existing custom bots appear as service_account ' +
1167+ 'credentials in environment/credentials.json, each with its requestUrl.' ,
1168+ requestUrlPattern : '{baseUrl}/api/webhooks/slack/custom/{credentialId}' ,
1169+ capabilities : SLACK_CAPABILITIES . map ( ( c ) => ( {
1170+ id : c . id ,
1171+ label : c . label ,
1172+ group : c . group ,
1173+ defaultChecked : c . defaultChecked ,
1174+ scopes : c . scopes ,
1175+ events : c . events ,
1176+ } ) ) ,
1177+ defaultManifest : buildSlackManifest ( new Set ( defaults ) , {
1178+ appName : 'Sim Bot' ,
1179+ webhookUrl : '<the credential requestUrl>' ,
1180+ } ) ,
1181+ }
1182+ }
1183+
11401184/**
11411185 * Serialize a trigger schema for VFS components/triggers/{provider}/{id}.json
11421186 */
@@ -1160,6 +1204,7 @@ export function serializeTriggerSchema(trigger: {
11601204 webhook : trigger . webhook || undefined ,
11611205 subBlocks : trigger . subBlocks . map ( serializeSubBlock ) ,
11621206 outputs : trigger . outputs ,
1207+ ...( trigger . id === 'slack_oauth' ? { setup : slackOAuthSetupReference ( ) } : { } ) ,
11631208 } ,
11641209 null ,
11651210 2
0 commit comments