diff --git a/apps/docs/content/docs/en/tools/google_books.mdx b/apps/docs/content/docs/en/tools/google_books.mdx
index 9baec6846..2b370d139 100644
--- a/apps/docs/content/docs/en/tools/google_books.mdx
+++ b/apps/docs/content/docs/en/tools/google_books.mdx
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## Usage Instructions
diff --git a/apps/docs/content/docs/en/tools/s3.mdx b/apps/docs/content/docs/en/tools/s3.mdx
index 95715f0f1..f7780eb58 100644
--- a/apps/docs/content/docs/en/tools/s3.mdx
+++ b/apps/docs/content/docs/en/tools/s3.mdx
@@ -71,6 +71,7 @@ Retrieve an object from an AWS S3 bucket
| --------- | ---- | -------- | ----------- |
| `accessKeyId` | string | Yes | Your AWS Access Key ID |
| `secretAccessKey` | string | Yes | Your AWS Secret Access Key |
+| `region` | string | No | Optional region override when URL does not include region \(e.g., us-east-1, eu-west-1\) |
| `s3Uri` | string | Yes | S3 Object URL \(e.g., https://bucket.s3.region.amazonaws.com/path/to/file\) |
#### Output
diff --git a/apps/docs/content/docs/en/tools/slack.mdx b/apps/docs/content/docs/en/tools/slack.mdx
index 35562a17e..0f4285e2a 100644
--- a/apps/docs/content/docs/en/tools/slack.mdx
+++ b/apps/docs/content/docs/en/tools/slack.mdx
@@ -79,7 +79,7 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format
| `channel` | string | No | Slack channel ID \(e.g., C1234567890\) |
| `dmUserId` | string | No | Slack user ID for direct messages \(e.g., U1234567890\) |
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
-| `thread_ts` | string | No | Thread timestamp to reply to \(creates thread reply\) |
+| `threadTs` | string | No | Thread timestamp to reply to \(creates thread reply\) |
| `files` | file[] | No | Files to attach to the message |
#### Output
diff --git a/apps/sim/app/api/wand/route.ts b/apps/sim/app/api/wand/route.ts
index aba3b14ef..f6089ff1d 100644
--- a/apps/sim/app/api/wand/route.ts
+++ b/apps/sim/app/api/wand/route.ts
@@ -238,6 +238,11 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
finalSystemPrompt += currentTimeContext
}
+ if (generationType === 'cron-expression') {
+ finalSystemPrompt +=
+ '\n\nIMPORTANT: Return ONLY the raw cron expression (e.g., "0 9 * * 1-5"). Do NOT wrap it in markdown code blocks, backticks, or quotes. Do NOT include any explanation or text before or after the expression.'
+ }
+
if (generationType === 'json-object') {
finalSystemPrompt +=
'\n\nIMPORTANT: Return ONLY the raw JSON object. Do NOT wrap it in markdown code blocks (no ```json or ```). Do NOT include any explanation or text before or after the JSON. The response must start with { and end with }.'
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx
index f92b8150a..e8fe08e5b 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx
@@ -1741,36 +1741,97 @@ export const ToolInput = memo(function ToolInput({
) : null
})()}
- {requiresOAuth && oauthConfig && (
-
-
- Account *
-
-
-
- handleParamChange(toolIndex, 'credential', value)
- }
- provider={oauthConfig.provider as OAuthProvider}
- requiredScopes={
- toolBlock?.subBlocks?.find((sb) => sb.id === 'credential')
- ?.requiredScopes ||
- getCanonicalScopesForProvider(oauthConfig.provider)
- }
- serviceId={oauthConfig.provider}
- disabled={disabled}
- />
-
-
- )}
-
{(() => {
const renderedElements: React.ReactNode[] = []
+ const showOAuth =
+ requiresOAuth && oauthConfig && tool.params?.authMethod !== 'bot_token'
+
+ const renderOAuthAccount = (): React.ReactNode => {
+ if (!showOAuth || !oauthConfig) return null
+ const credentialSubBlock = toolBlock?.subBlocks?.find(
+ (s) => s.type === 'oauth-input'
+ )
+ return (
+
+
+ {credentialSubBlock?.title || 'Account'}{' '}
+ *
+
+
+
+ handleParamChange(toolIndex, 'credential', value)
+ }
+ provider={oauthConfig.provider as OAuthProvider}
+ requiredScopes={
+ credentialSubBlock?.requiredScopes ||
+ getCanonicalScopesForProvider(oauthConfig.provider)
+ }
+ serviceId={oauthConfig.provider}
+ disabled={disabled}
+ />
+
+
+ )
+ }
+
+ const renderSubBlock = (sb: BlockSubBlockConfig): React.ReactNode => {
+ const effectiveParamId = sb.id
+ const canonicalId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
+ const canonicalGroup = canonicalId
+ ? toolCanonicalIndex?.groupsById[canonicalId]
+ : undefined
+ const hasCanonicalPair = isCanonicalPair(canonicalGroup)
+ const canonicalMode =
+ canonicalGroup && hasCanonicalPair
+ ? resolveCanonicalMode(
+ canonicalGroup,
+ { operation: tool.operation, ...tool.params },
+ toolScopedOverrides
+ )
+ : undefined
+
+ const canonicalToggleProp =
+ hasCanonicalPair && canonicalMode && canonicalId
+ ? {
+ mode: canonicalMode,
+ onToggle: () => {
+ const nextMode = canonicalMode === 'advanced' ? 'basic' : 'advanced'
+ collaborativeSetBlockCanonicalMode(
+ blockId,
+ `${tool.type}:${canonicalId}`,
+ nextMode
+ )
+ },
+ }
+ : undefined
+
+ const sbWithTitle = sb.title
+ ? sb
+ : { ...sb, title: formatParameterLabel(effectiveParamId) }
+
+ return (
+
+ )
+ }
+
if (useSubBlocks && displaySubBlocks.length > 0) {
+ const allBlockSubBlocks = toolBlock?.subBlocks || []
const coveredParamIds = new Set(
- displaySubBlocks.flatMap((sb) => {
+ allBlockSubBlocks.flatMap((sb) => {
const ids = [sb.id]
if (sb.canonicalParamId) ids.push(sb.canonicalParamId)
const cId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
@@ -1785,57 +1846,45 @@ export const ToolInput = memo(function ToolInput({
})
)
- displaySubBlocks.forEach((sb) => {
- const effectiveParamId = sb.id
- const canonicalId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
- const canonicalGroup = canonicalId
- ? toolCanonicalIndex?.groupsById[canonicalId]
- : undefined
- const hasCanonicalPair = isCanonicalPair(canonicalGroup)
- const canonicalMode =
- canonicalGroup && hasCanonicalPair
- ? resolveCanonicalMode(
- canonicalGroup,
- { operation: tool.operation, ...tool.params },
- toolScopedOverrides
- )
- : undefined
+ type RenderItem =
+ | { kind: 'subblock'; sb: BlockSubBlockConfig }
+ | { kind: 'oauth' }
- const canonicalToggleProp =
- hasCanonicalPair && canonicalMode && canonicalId
- ? {
- mode: canonicalMode,
- onToggle: () => {
- const nextMode =
- canonicalMode === 'advanced' ? 'basic' : 'advanced'
- collaborativeSetBlockCanonicalMode(
- blockId,
- `${tool.type}:${canonicalId}`,
- nextMode
- )
- },
- }
- : undefined
+ const renderOrder: RenderItem[] = displaySubBlocks.map((sb) => ({
+ kind: 'subblock' as const,
+ sb,
+ }))
- const sbWithTitle = sb.title
- ? sb
- : { ...sb, title: formatParameterLabel(effectiveParamId) }
-
- renderedElements.push(
-
+ if (showOAuth) {
+ const credentialIdx = allBlockSubBlocks.findIndex(
+ (sb) => sb.type === 'oauth-input'
)
- })
+ if (credentialIdx >= 0) {
+ const sbPositions = new Map(allBlockSubBlocks.map((sb, i) => [sb.id, i]))
+ const insertAt = renderOrder.findIndex(
+ (item) =>
+ item.kind === 'subblock' &&
+ (sbPositions.get(item.sb.id) ?? Number.POSITIVE_INFINITY) >
+ credentialIdx
+ )
+ if (insertAt === -1) {
+ renderOrder.push({ kind: 'oauth' })
+ } else {
+ renderOrder.splice(insertAt, 0, { kind: 'oauth' })
+ }
+ } else {
+ renderOrder.unshift({ kind: 'oauth' })
+ }
+ }
+
+ for (const item of renderOrder) {
+ if (item.kind === 'oauth') {
+ const el = renderOAuthAccount()
+ if (el) renderedElements.push(el)
+ } else {
+ renderedElements.push(renderSubBlock(item.sb))
+ }
+ }
const uncoveredParams = displayParams.filter(
(param) =>
@@ -1873,6 +1922,11 @@ export const ToolInput = memo(function ToolInput({
)
}
+ {
+ const el = renderOAuthAccount()
+ if (el) renderedElements.push(el)
+ }
+
const filteredParams = displayParams.filter((param) =>
evaluateParameterCondition(param, tool)
)
diff --git a/apps/sim/blocks/blocks/schedule.ts b/apps/sim/blocks/blocks/schedule.ts
index fb757543e..0757eca0e 100644
--- a/apps/sim/blocks/blocks/schedule.ts
+++ b/apps/sim/blocks/blocks/schedule.ts
@@ -122,6 +122,25 @@ export const ScheduleBlock: BlockConfig = {
required: true,
mode: 'trigger',
condition: { field: 'scheduleType', value: 'custom' },
+ wandConfig: {
+ enabled: true,
+ prompt: `You are an expert at writing cron expressions. Generate a valid cron expression based on the user's description.
+
+Cron format: minute hour day-of-month month day-of-week
+- minute: 0-59
+- hour: 0-23
+- day-of-month: 1-31
+- month: 1-12
+- day-of-week: 0-7 (0 and 7 are Sunday)
+
+Special characters: * (any), , (list), - (range), / (step)
+
+{context}
+
+Return ONLY the cron expression, nothing else. No explanation, no backticks, no quotes.`,
+ placeholder: 'Describe your schedule (e.g., "every weekday at 9am")',
+ generationType: 'cron-expression',
+ },
},
{
diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts
index 8ac262bef..30158d734 100644
--- a/apps/sim/blocks/types.ts
+++ b/apps/sim/blocks/types.ts
@@ -40,6 +40,7 @@ export type GenerationType =
| 'neo4j-parameters'
| 'timestamp'
| 'timezone'
+ | 'cron-expression'
export type SubBlockType =
| 'short-input' // Single line input
diff --git a/apps/sim/tools/params.ts b/apps/sim/tools/params.ts
index 89a9d0f8d..66bbdc292 100644
--- a/apps/sim/tools/params.ts
+++ b/apps/sim/tools/params.ts
@@ -827,11 +827,10 @@ export function formatParameterLabel(paramId: string): string {
}
/**
- * SubBlock IDs that are "structural" — they control tool routing or auth,
- * not user-facing parameters. These are excluded from tool-input rendering
- * unless they have an explicit paramVisibility set.
+ * SubBlock IDs that control tool routing, not user-facing parameters.
+ * Excluded from tool-input rendering unless they have an explicit paramVisibility set.
*/
-const STRUCTURAL_SUBBLOCK_IDS = new Set(['operation', 'authMethod', 'destinationType'])
+const STRUCTURAL_SUBBLOCK_IDS = new Set(['operation'])
/**
* SubBlock types that represent auth/credential inputs handled separately
@@ -955,12 +954,8 @@ export function getSubBlocksForToolInput(
} else if (sb.id in toolParamVisibility) {
visibility = toolParamVisibility[sb.id]
} else if (sb.canonicalParamId) {
- // SubBlock has a canonicalParamId that doesn't directly match a tool param.
- // This means the block's params() function transforms it before sending to the tool
- // (e.g. listFolderId → folderId). These are user-facing inputs, default to user-or-llm.
visibility = 'user-or-llm'
} else {
- // SubBlock has no corresponding tool param — skip it
continue
}
}
diff --git a/apps/sim/tools/slack/message.ts b/apps/sim/tools/slack/message.ts
index 1fd60b685..b1f047403 100644
--- a/apps/sim/tools/slack/message.ts
+++ b/apps/sim/tools/slack/message.ts
@@ -57,7 +57,7 @@ export const slackMessageTool: ToolConfig