From 38be2b76c426f1a9ac13476a8aacb1d71854ded1 Mon Sep 17 00:00:00 2001 From: Adam Gough <77861281+aadamgough@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:37:14 -0800 Subject: [PATCH] fix(slack): respect message limit, remove duplicate canonical representations (#2469) * fix(slack): respect message limit, remove duplicate canonical representations * removed comment * updated docs script --------- Co-authored-by: aadamgough --- apps/docs/components/ui/icon-mapping.ts | 224 +++++++++--------- apps/docs/content/docs/en/tools/slack.mdx | 2 +- .../api/tools/slack/read-messages/route.ts | 11 +- apps/sim/blocks/blocks/microsoft_planner.ts | 1 - apps/sim/blocks/blocks/slack.ts | 50 ++-- apps/sim/blocks/blocks/wealthbox.ts | 17 +- apps/sim/tools/slack/message_reader.ts | 2 +- scripts/generate-docs.ts | 5 +- 8 files changed, 141 insertions(+), 171 deletions(-) diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 56efde9f2..6e4dd1d32 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -120,117 +120,117 @@ import { type IconComponent = ComponentType> export const blockTypeToIconMap: Record = { - zoom: ZoomIcon, - zep: ZepIcon, - zendesk: ZendeskIcon, - youtube: YouTubeIcon, - x: xIcon, - wordpress: WordpressIcon, - wikipedia: WikipediaIcon, - whatsapp: WhatsAppIcon, - webflow: WebflowIcon, - wealthbox: WealthboxIcon, - vision: EyeIcon, - video_generator: VideoIcon, - typeform: TypeformIcon, - twilio_voice: TwilioIcon, - twilio_sms: TwilioIcon, - tts: TTSIcon, - trello: TrelloIcon, - translate: TranslateIcon, - thinking: BrainIcon, - telegram: TelegramIcon, - tavily: TavilyIcon, - supabase: SupabaseIcon, - stt: STTIcon, - stripe: StripeIcon, - stagehand: StagehandIcon, - ssh: SshIcon, - sqs: SQSIcon, - spotify: SpotifyIcon, - smtp: SmtpIcon, - slack: SlackIcon, - shopify: ShopifyIcon, - sharepoint: MicrosoftSharepointIcon, - sftp: SftpIcon, - servicenow: ServiceNowIcon, - serper: SerperIcon, - sentry: SentryIcon, - sendgrid: SendgridIcon, - search: SearchIcon, - salesforce: SalesforceIcon, - s3: S3Icon, - resend: ResendIcon, - reddit: RedditIcon, - rds: RDSIcon, - qdrant: QdrantIcon, - posthog: PosthogIcon, - postgresql: PostgresIcon, - polymarket: PolymarketIcon, - pipedrive: PipedriveIcon, - pinecone: PineconeIcon, - perplexity: PerplexityIcon, - parallel_ai: ParallelIcon, - outlook: OutlookIcon, - openai: OpenAIIcon, - onedrive: MicrosoftOneDriveIcon, - notion: NotionIcon, - neo4j: Neo4jIcon, - mysql: MySQLIcon, - mongodb: MongoDBIcon, - mistral_parse: MistralIcon, - microsoft_teams: MicrosoftTeamsIcon, - microsoft_planner: MicrosoftPlannerIcon, - microsoft_excel: MicrosoftExcelIcon, - memory: BrainIcon, - mem0: Mem0Icon, - mailgun: MailgunIcon, - mailchimp: MailchimpIcon, - linkup: LinkupIcon, - linkedin: LinkedInIcon, - linear: LinearIcon, - knowledge: PackageSearchIcon, - kalshi: KalshiIcon, - jira: JiraIcon, - jina: JinaAIIcon, - intercom: IntercomIcon, - incidentio: IncidentioIcon, - image_generator: ImageIcon, - hunter: HunterIOIcon, - huggingface: HuggingFaceIcon, - hubspot: HubspotIcon, - grafana: GrafanaIcon, - google_vault: GoogleVaultIcon, - google_slides: GoogleSlidesIcon, - google_sheets: GoogleSheetsIcon, - google_groups: GoogleGroupsIcon, - google_forms: GoogleFormsIcon, - google_drive: GoogleDriveIcon, - google_docs: GoogleDocsIcon, - google_calendar: GoogleCalendarIcon, - google_search: GoogleIcon, - gmail: GmailIcon, - gitlab: GitLabIcon, - github: GithubIcon, - firecrawl: FirecrawlIcon, - file: DocumentIcon, - exa: ExaAIIcon, - elevenlabs: ElevenLabsIcon, - elasticsearch: ElasticsearchIcon, - dynamodb: DynamoDBIcon, - duckduckgo: DuckDuckGoIcon, - dropbox: DropboxIcon, - discord: DiscordIcon, - datadog: DatadogIcon, - cursor: CursorIcon, - confluence: ConfluenceIcon, - clay: ClayIcon, - calendly: CalendlyIcon, - browser_use: BrowserUseIcon, - asana: AsanaIcon, - arxiv: ArxivIcon, - apollo: ApolloIcon, - apify: ApifyIcon, - airtable: AirtableIcon, ahrefs: AhrefsIcon, + airtable: AirtableIcon, + apify: ApifyIcon, + apollo: ApolloIcon, + arxiv: ArxivIcon, + asana: AsanaIcon, + browser_use: BrowserUseIcon, + calendly: CalendlyIcon, + clay: ClayIcon, + confluence: ConfluenceIcon, + cursor: CursorIcon, + datadog: DatadogIcon, + discord: DiscordIcon, + dropbox: DropboxIcon, + duckduckgo: DuckDuckGoIcon, + dynamodb: DynamoDBIcon, + elasticsearch: ElasticsearchIcon, + elevenlabs: ElevenLabsIcon, + exa: ExaAIIcon, + file: DocumentIcon, + firecrawl: FirecrawlIcon, + github: GithubIcon, + gitlab: GitLabIcon, + gmail: GmailIcon, + google_calendar: GoogleCalendarIcon, + google_docs: GoogleDocsIcon, + google_drive: GoogleDriveIcon, + google_forms: GoogleFormsIcon, + google_groups: GoogleGroupsIcon, + google_search: GoogleIcon, + google_sheets: GoogleSheetsIcon, + google_slides: GoogleSlidesIcon, + google_vault: GoogleVaultIcon, + grafana: GrafanaIcon, + hubspot: HubspotIcon, + huggingface: HuggingFaceIcon, + hunter: HunterIOIcon, + image_generator: ImageIcon, + incidentio: IncidentioIcon, + intercom: IntercomIcon, + jina: JinaAIIcon, + jira: JiraIcon, + kalshi: KalshiIcon, + knowledge: PackageSearchIcon, + linear: LinearIcon, + linkedin: LinkedInIcon, + linkup: LinkupIcon, + mailchimp: MailchimpIcon, + mailgun: MailgunIcon, + mem0: Mem0Icon, + memory: BrainIcon, + microsoft_excel: MicrosoftExcelIcon, + microsoft_planner: MicrosoftPlannerIcon, + microsoft_teams: MicrosoftTeamsIcon, + mistral_parse: MistralIcon, + mongodb: MongoDBIcon, + mysql: MySQLIcon, + neo4j: Neo4jIcon, + notion: NotionIcon, + onedrive: MicrosoftOneDriveIcon, + openai: OpenAIIcon, + outlook: OutlookIcon, + parallel_ai: ParallelIcon, + perplexity: PerplexityIcon, + pinecone: PineconeIcon, + pipedrive: PipedriveIcon, + polymarket: PolymarketIcon, + postgresql: PostgresIcon, + posthog: PosthogIcon, + qdrant: QdrantIcon, + rds: RDSIcon, + reddit: RedditIcon, + resend: ResendIcon, + s3: S3Icon, + salesforce: SalesforceIcon, + search: SearchIcon, + sendgrid: SendgridIcon, + sentry: SentryIcon, + serper: SerperIcon, + servicenow: ServiceNowIcon, + sftp: SftpIcon, + sharepoint: MicrosoftSharepointIcon, + shopify: ShopifyIcon, + slack: SlackIcon, + smtp: SmtpIcon, + spotify: SpotifyIcon, + sqs: SQSIcon, + ssh: SshIcon, + stagehand: StagehandIcon, + stripe: StripeIcon, + stt: STTIcon, + supabase: SupabaseIcon, + tavily: TavilyIcon, + telegram: TelegramIcon, + thinking: BrainIcon, + translate: TranslateIcon, + trello: TrelloIcon, + tts: TTSIcon, + twilio_sms: TwilioIcon, + twilio_voice: TwilioIcon, + typeform: TypeformIcon, + video_generator: VideoIcon, + vision: EyeIcon, + wealthbox: WealthboxIcon, + webflow: WebflowIcon, + whatsapp: WhatsAppIcon, + wikipedia: WikipediaIcon, + wordpress: WordpressIcon, + x: xIcon, + youtube: YouTubeIcon, + zendesk: ZendeskIcon, + zep: ZepIcon, + zoom: ZoomIcon, } diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx index 3ebb64d63..7eb927c81 100644 --- a/apps/docs/content/docs/en/tools/slack.mdx +++ b/apps/docs/content/docs/en/tools/slack.mdx @@ -114,7 +114,7 @@ Read the latest messages from Slack channels. Retrieve conversation history with | `botToken` | string | No | Bot token for Custom Bot | | `channel` | string | No | Slack channel to read messages from \(e.g., #general\) | | `userId` | string | No | User ID for DM conversation \(e.g., U1234567890\) | -| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 100\) | +| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 15\) | | `oldest` | string | No | Start of time range \(timestamp\) | | `latest` | string | No | End of time range \(timestamp\) | diff --git a/apps/sim/app/api/tools/slack/read-messages/route.ts b/apps/sim/app/api/tools/slack/read-messages/route.ts index 74d9d9742..c0a87c3cf 100644 --- a/apps/sim/app/api/tools/slack/read-messages/route.ts +++ b/apps/sim/app/api/tools/slack/read-messages/route.ts @@ -14,7 +14,12 @@ const SlackReadMessagesSchema = z accessToken: z.string().min(1, 'Access token is required'), channel: z.string().optional().nullable(), userId: z.string().optional().nullable(), - limit: z.number().optional().nullable(), + limit: z.coerce + .number() + .min(1, 'Limit must be at least 1') + .max(15, 'Limit cannot exceed 15') + .optional() + .nullable(), oldest: z.string().optional().nullable(), latest: z.string().optional().nullable(), }) @@ -62,8 +67,8 @@ export async function POST(request: NextRequest) { const url = new URL('https://slack.com/api/conversations.history') url.searchParams.append('channel', channel!) - const limit = validatedData.limit ? Number(validatedData.limit) : 10 - url.searchParams.append('limit', String(Math.min(limit, 15))) + const limit = validatedData.limit ?? 10 + url.searchParams.append('limit', String(limit)) if (validatedData.oldest) { url.searchParams.append('oldest', validatedData.oldest) diff --git a/apps/sim/blocks/blocks/microsoft_planner.ts b/apps/sim/blocks/blocks/microsoft_planner.ts index f8880a74a..ff93b8ca2 100644 --- a/apps/sim/blocks/blocks/microsoft_planner.ts +++ b/apps/sim/blocks/blocks/microsoft_planner.ts @@ -134,7 +134,6 @@ export const MicrosoftPlannerBlock: BlockConfig = { placeholder: 'Enter the bucket ID', condition: { field: 'operation', value: ['read_bucket', 'update_bucket', 'delete_bucket'] }, dependsOn: ['credential'], - canonicalParamId: 'bucketId', }, // ETag for update/delete operations diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index f5c16c1b8..77881f44f 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -181,7 +181,6 @@ export const SlackBlock: BlockConfig = { id: 'threadTs', title: 'Thread Timestamp', type: 'short-input', - canonicalParamId: 'thread_ts', placeholder: 'Reply to thread (e.g., 1405894322.002768)', condition: { field: 'operation', @@ -263,7 +262,6 @@ export const SlackBlock: BlockConfig = { id: 'channelLimit', title: 'Channel Limit', type: 'short-input', - canonicalParamId: 'limit', placeholder: '100', condition: { field: 'operation', @@ -275,7 +273,6 @@ export const SlackBlock: BlockConfig = { id: 'memberLimit', title: 'Member Limit', type: 'short-input', - canonicalParamId: 'limit', placeholder: '100', condition: { field: 'operation', @@ -301,7 +298,6 @@ export const SlackBlock: BlockConfig = { id: 'userLimit', title: 'User Limit', type: 'short-input', - canonicalParamId: 'limit', placeholder: '100', condition: { field: 'operation', @@ -358,7 +354,6 @@ export const SlackBlock: BlockConfig = { id: 'updateTimestamp', title: 'Message Timestamp', type: 'short-input', - canonicalParamId: 'timestamp', placeholder: 'Message timestamp (e.g., 1405894322.002768)', condition: { field: 'operation', @@ -382,7 +377,6 @@ export const SlackBlock: BlockConfig = { id: 'deleteTimestamp', title: 'Message Timestamp', type: 'short-input', - canonicalParamId: 'timestamp', placeholder: 'Message timestamp (e.g., 1405894322.002768)', condition: { field: 'operation', @@ -395,7 +389,6 @@ export const SlackBlock: BlockConfig = { id: 'reactionTimestamp', title: 'Message Timestamp', type: 'short-input', - canonicalParamId: 'timestamp', placeholder: 'Message timestamp (e.g., 1405894322.002768)', condition: { field: 'operation', @@ -407,7 +400,6 @@ export const SlackBlock: BlockConfig = { id: 'emojiName', title: 'Emoji Name', type: 'short-input', - canonicalParamId: 'name', placeholder: 'Emoji name without colons (e.g., thumbsup, heart, eyes)', condition: { field: 'operation', @@ -554,47 +546,35 @@ export const SlackBlock: BlockConfig = { baseParams.content = content break - case 'read': - if (limit) { - const parsedLimit = Number.parseInt(limit, 10) - baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 10 - } else { - baseParams.limit = 10 + case 'read': { + const parsedLimit = limit ? Number.parseInt(limit, 10) : 10 + if (Number.isNaN(parsedLimit) || parsedLimit < 1 || parsedLimit > 15) { + throw new Error('Message limit must be between 1 and 15') } + baseParams.limit = parsedLimit if (oldest) { baseParams.oldest = oldest } break + } - case 'list_channels': + case 'list_channels': { baseParams.includePrivate = includePrivate !== 'false' baseParams.excludeArchived = true - if (channelLimit) { - const parsedLimit = Number.parseInt(channelLimit, 10) - baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100 - } else { - baseParams.limit = 100 - } + baseParams.limit = channelLimit ? Number.parseInt(channelLimit, 10) : 100 break + } - case 'list_members': - if (memberLimit) { - const parsedLimit = Number.parseInt(memberLimit, 10) - baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100 - } else { - baseParams.limit = 100 - } + case 'list_members': { + baseParams.limit = memberLimit ? Number.parseInt(memberLimit, 10) : 100 break + } - case 'list_users': + case 'list_users': { baseParams.includeDeleted = includeDeleted === 'true' - if (userLimit) { - const parsedLimit = Number.parseInt(userLimit, 10) - baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100 - } else { - baseParams.limit = 100 - } + baseParams.limit = userLimit ? Number.parseInt(userLimit, 10) : 100 break + } case 'get_user': if (!userId) { diff --git a/apps/sim/blocks/blocks/wealthbox.ts b/apps/sim/blocks/blocks/wealthbox.ts index d1d094deb..b12a0c8ce 100644 --- a/apps/sim/blocks/blocks/wealthbox.ts +++ b/apps/sim/blocks/blocks/wealthbox.ts @@ -70,17 +70,6 @@ export const WealthboxBlock: BlockConfig = { title: 'Task ID', type: 'short-input', placeholder: 'Enter Task ID', - mode: 'basic', - canonicalParamId: 'taskId', - condition: { field: 'operation', value: ['read_task'] }, - }, - { - id: 'manualTaskId', - title: 'Task ID', - type: 'short-input', - canonicalParamId: 'taskId', - placeholder: 'Enter Task ID', - mode: 'advanced', condition: { field: 'operation', value: ['read_task'] }, }, { @@ -167,12 +156,9 @@ export const WealthboxBlock: BlockConfig = { } }, params: (params) => { - const { credential, operation, contactId, manualContactId, taskId, manualTaskId, ...rest } = - params + const { credential, operation, contactId, manualContactId, taskId, ...rest } = params - // Handle both selector and manual inputs const effectiveContactId = (contactId || manualContactId || '').trim() - const effectiveTaskId = (taskId || manualTaskId || '').trim() const baseParams = { ...rest, @@ -225,7 +211,6 @@ export const WealthboxBlock: BlockConfig = { contactId: { type: 'string', description: 'Contact identifier' }, manualContactId: { type: 'string', description: 'Manual contact identifier' }, taskId: { type: 'string', description: 'Task identifier' }, - manualTaskId: { type: 'string', description: 'Manual task identifier' }, content: { type: 'string', description: 'Content text' }, firstName: { type: 'string', description: 'First name' }, lastName: { type: 'string', description: 'Last name' }, diff --git a/apps/sim/tools/slack/message_reader.ts b/apps/sim/tools/slack/message_reader.ts index a937007e4..b7bdc258f 100644 --- a/apps/sim/tools/slack/message_reader.ts +++ b/apps/sim/tools/slack/message_reader.ts @@ -51,7 +51,7 @@ export const slackMessageReaderTool: ToolConfig< type: 'number', required: false, visibility: 'user-or-llm', - description: 'Number of messages to retrieve (default: 10, max: 100)', + description: 'Number of messages to retrieve (default: 10, max: 15)', }, oldest: { type: 'string', diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index bccd8cfce..d8c24114f 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -71,7 +71,7 @@ async function generateIconMapping(): Promise> { console.log('Generating icon mapping from block definitions...') const iconMapping: Record = {} - const blockFiles = await glob(`${BLOCKS_PATH}/*.ts`) + const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort() for (const blockFile of blockFiles) { const fileContent = fs.readFileSync(blockFile, 'utf-8') @@ -132,6 +132,7 @@ function writeIconMapping(iconMapping: Record): void { // Generate mapping with direct references (no dynamic access for tree shaking) const mappingEntries = Object.entries(iconMapping) + .sort(([a], [b]) => a.localeCompare(b)) .map(([blockType, iconName]) => ` ${blockType}: ${iconName},`) .join('\n') @@ -1165,7 +1166,7 @@ async function generateAllBlockDocs() { const iconMapping = await generateIconMapping() writeIconMapping(iconMapping) - const blockFiles = await glob(`${BLOCKS_PATH}/*.ts`) + const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort() for (const blockFile of blockFiles) { await generateBlockDoc(blockFile)