Compare commits

..

1 Commits

Author SHA1 Message Date
Cursor Agent
ccb15dae09 Reduce copilot scroll stickiness for consistent, less-sticky behavior
- Changed default stickinessThreshold from 100 to 30 in use-scroll-management.ts
- Removed explicit stickinessThreshold override (40) from copilot.tsx
- Both copilot and chat now use the same default value of 30
- This makes scrolling less sticky across all copilot message interactions

Co-authored-by: Emir Karabeg <emir-karabeg@users.noreply.github.com>
2026-02-14 01:04:39 +00:00
17 changed files with 140 additions and 242 deletions

View File

@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard <BlockInfoCard
type="google_books" type="google_books"
color="#E0E0E0" color="#FFFFFF"
/> />
## Usage Instructions ## Usage Instructions

View File

@@ -71,7 +71,6 @@ Retrieve an object from an AWS S3 bucket
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `accessKeyId` | string | Yes | Your AWS Access Key ID | | `accessKeyId` | string | Yes | Your AWS Access Key ID |
| `secretAccessKey` | string | Yes | Your AWS Secret Access Key | | `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\) | | `s3Uri` | string | Yes | S3 Object URL \(e.g., https://bucket.s3.region.amazonaws.com/path/to/file\) |
#### Output #### Output

View File

@@ -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\) | | `channel` | string | No | Slack channel ID \(e.g., C1234567890\) |
| `dmUserId` | string | No | Slack user ID for direct messages \(e.g., U1234567890\) | | `dmUserId` | string | No | Slack user ID for direct messages \(e.g., U1234567890\) |
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) | | `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
| `threadTs` | string | No | Thread timestamp to reply to \(creates thread reply\) | | `thread_ts` | string | No | Thread timestamp to reply to \(creates thread reply\) |
| `files` | file[] | No | Files to attach to the message | | `files` | file[] | No | Files to attach to the message |
#### Output #### Output

View File

@@ -238,11 +238,6 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
finalSystemPrompt += currentTimeContext 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') { if (generationType === 'json-object') {
finalSystemPrompt += 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 }.' '\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 }.'

View File

@@ -131,10 +131,8 @@ export const Copilot = forwardRef<CopilotRef, CopilotProps>(({ panelWidth }, ref
resumeActiveStream, resumeActiveStream,
}) })
// Handle scroll management (80px stickiness for copilot) // Handle scroll management
const { scrollAreaRef, scrollToBottom } = useScrollManagement(messages, isSendingMessage, { const { scrollAreaRef, scrollToBottom } = useScrollManagement(messages, isSendingMessage)
stickinessThreshold: 40,
})
// Handle chat history grouping // Handle chat history grouping
const { groupedChats, handleHistoryDropdownOpen: handleHistoryDropdownOpenHook } = useChatHistory( const { groupedChats, handleHistoryDropdownOpen: handleHistoryDropdownOpenHook } = useChatHistory(

View File

@@ -1,10 +1,9 @@
'use client' 'use client'
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import { SubBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block' import { SubBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block'
import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types' import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
interface ToolSubBlockRendererProps { interface ToolSubBlockRendererProps {
blockId: string blockId: string
@@ -45,43 +44,53 @@ export function ToolSubBlockRenderer({
canonicalToggle, canonicalToggle,
}: ToolSubBlockRendererProps) { }: ToolSubBlockRendererProps) {
const syntheticId = `${subBlockId}-tool-${toolIndex}-${effectiveParamId}` const syntheticId = `${subBlockId}-tool-${toolIndex}-${effectiveParamId}`
const [storeValue, setStoreValue] = useSubBlockValue(blockId, syntheticId)
const toolParamValue = toolParams?.[effectiveParamId] ?? '' const toolParamValue = toolParams?.[effectiveParamId] ?? ''
const isObjectType = OBJECT_SUBBLOCK_TYPES.has(subBlock.type) const isObjectType = OBJECT_SUBBLOCK_TYPES.has(subBlock.type)
const syncedRef = useRef<string | null>(null) const lastPushedToStoreRef = useRef<string | null>(null)
const onParamChangeRef = useRef(onParamChange) const lastPushedToParamsRef = useRef<string | null>(null)
onParamChangeRef.current = onParamChange
useEffect(() => { useEffect(() => {
const unsub = useSubBlockStore.subscribe((state, prevState) => { if (!toolParamValue && lastPushedToStoreRef.current === null) {
const wfId = useWorkflowRegistry.getState().activeWorkflowId lastPushedToStoreRef.current = toolParamValue
if (!wfId) return lastPushedToParamsRef.current = toolParamValue
const newVal = state.workflowValues[wfId]?.[blockId]?.[syntheticId] return
const oldVal = prevState.workflowValues[wfId]?.[blockId]?.[syntheticId]
if (newVal === oldVal) return
const stringified =
newVal == null ? '' : typeof newVal === 'string' ? newVal : JSON.stringify(newVal)
if (stringified === syncedRef.current) return
syncedRef.current = stringified
onParamChangeRef.current(toolIndex, effectiveParamId, stringified)
})
return unsub
}, [blockId, syntheticId, toolIndex, effectiveParamId])
useEffect(() => {
if (toolParamValue === syncedRef.current) return
syncedRef.current = toolParamValue
if (isObjectType && toolParamValue) {
try {
const parsed = JSON.parse(toolParamValue)
if (typeof parsed === 'object' && parsed !== null) {
useSubBlockStore.getState().setValue(blockId, syntheticId, parsed)
return
}
} catch {}
} }
useSubBlockStore.getState().setValue(blockId, syntheticId, toolParamValue) if (toolParamValue !== lastPushedToStoreRef.current) {
}, [toolParamValue, blockId, syntheticId, isObjectType]) lastPushedToStoreRef.current = toolParamValue
lastPushedToParamsRef.current = toolParamValue
if (isObjectType && typeof toolParamValue === 'string' && toolParamValue) {
try {
const parsed = JSON.parse(toolParamValue)
if (typeof parsed === 'object' && parsed !== null) {
setStoreValue(parsed)
return
}
} catch {
// Not valid JSON — fall through to set as string
}
}
setStoreValue(toolParamValue)
}
}, [toolParamValue, setStoreValue, isObjectType])
useEffect(() => {
if (storeValue == null && lastPushedToParamsRef.current === null) return
const stringValue =
storeValue == null
? ''
: typeof storeValue === 'string'
? storeValue
: JSON.stringify(storeValue)
if (stringValue !== lastPushedToParamsRef.current) {
lastPushedToParamsRef.current = stringValue
lastPushedToStoreRef.current = stringValue
onParamChange(toolIndex, effectiveParamId, stringValue)
}
}, [storeValue, toolIndex, effectiveParamId, onParamChange])
const visibility = subBlock.paramVisibility ?? 'user-or-llm' const visibility = subBlock.paramVisibility ?? 'user-or-llm'
const isOptionalForUser = visibility !== 'user-only' const isOptionalForUser = visibility !== 'user-only'

View File

@@ -1741,97 +1741,36 @@ export const ToolInput = memo(function ToolInput({
) : null ) : null
})()} })()}
{requiresOAuth && oauthConfig && (
<div className='relative min-w-0 space-y-[6px]'>
<div className='font-medium text-[13px] text-[var(--text-primary)]'>
Account <span className='ml-0.5'>*</span>
</div>
<div className='w-full min-w-0'>
<ToolCredentialSelector
value={tool.params?.credential || ''}
onChange={(value: string) =>
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}
/>
</div>
</div>
)}
{(() => { {(() => {
const renderedElements: React.ReactNode[] = [] 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 (
<div key='oauth-account' className='relative min-w-0 space-y-[6px]'>
<div className='font-medium text-[13px] text-[var(--text-primary)]'>
{credentialSubBlock?.title || 'Account'}{' '}
<span className='ml-0.5'>*</span>
</div>
<div className='w-full min-w-0'>
<ToolCredentialSelector
value={tool.params?.credential || ''}
onChange={(value: string) =>
handleParamChange(toolIndex, 'credential', value)
}
provider={oauthConfig.provider as OAuthProvider}
requiredScopes={
credentialSubBlock?.requiredScopes ||
getCanonicalScopesForProvider(oauthConfig.provider)
}
serviceId={oauthConfig.provider}
disabled={disabled}
/>
</div>
</div>
)
}
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 (
<ToolSubBlockRenderer
key={sb.id}
blockId={blockId}
subBlockId={subBlockId}
toolIndex={toolIndex}
subBlock={sbWithTitle}
effectiveParamId={effectiveParamId}
toolParams={tool.params}
onParamChange={handleParamChange}
disabled={disabled}
canonicalToggle={canonicalToggleProp}
/>
)
}
if (useSubBlocks && displaySubBlocks.length > 0) { if (useSubBlocks && displaySubBlocks.length > 0) {
const allBlockSubBlocks = toolBlock?.subBlocks || []
const coveredParamIds = new Set( const coveredParamIds = new Set(
allBlockSubBlocks.flatMap((sb) => { displaySubBlocks.flatMap((sb) => {
const ids = [sb.id] const ids = [sb.id]
if (sb.canonicalParamId) ids.push(sb.canonicalParamId) if (sb.canonicalParamId) ids.push(sb.canonicalParamId)
const cId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id] const cId = toolCanonicalIndex?.canonicalIdBySubBlockId[sb.id]
@@ -1846,45 +1785,57 @@ export const ToolInput = memo(function ToolInput({
}) })
) )
type RenderItem = displaySubBlocks.forEach((sb) => {
| { kind: 'subblock'; sb: BlockSubBlockConfig } const effectiveParamId = sb.id
| { kind: 'oauth' } 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 renderOrder: RenderItem[] = displaySubBlocks.map((sb) => ({ const canonicalToggleProp =
kind: 'subblock' as const, hasCanonicalPair && canonicalMode && canonicalId
sb, ? {
})) mode: canonicalMode,
onToggle: () => {
const nextMode =
canonicalMode === 'advanced' ? 'basic' : 'advanced'
collaborativeSetBlockCanonicalMode(
blockId,
`${tool.type}:${canonicalId}`,
nextMode
)
},
}
: undefined
if (showOAuth) { const sbWithTitle = sb.title
const credentialIdx = allBlockSubBlocks.findIndex( ? sb
(sb) => sb.type === 'oauth-input' : { ...sb, title: formatParameterLabel(effectiveParamId) }
renderedElements.push(
<ToolSubBlockRenderer
key={sb.id}
blockId={blockId}
subBlockId={subBlockId}
toolIndex={toolIndex}
subBlock={sbWithTitle}
effectiveParamId={effectiveParamId}
toolParams={tool.params}
onParamChange={handleParamChange}
disabled={disabled}
canonicalToggle={canonicalToggleProp}
/>
) )
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( const uncoveredParams = displayParams.filter(
(param) => (param) =>
@@ -1922,11 +1873,6 @@ export const ToolInput = memo(function ToolInput({
) )
} }
{
const el = renderOAuthAccount()
if (el) renderedElements.push(el)
}
const filteredParams = displayParams.filter((param) => const filteredParams = displayParams.filter((param) =>
evaluateParameterCondition(param, tool) evaluateParameterCondition(param, tool)
) )

View File

@@ -16,7 +16,7 @@ interface UseScrollManagementOptions {
/** /**
* Distance from bottom (in pixels) within which auto-scroll stays active * Distance from bottom (in pixels) within which auto-scroll stays active
* @remarks Lower values = less sticky (user can scroll away easier) * @remarks Lower values = less sticky (user can scroll away easier)
* @defaultValue 100 * @defaultValue 30
*/ */
stickinessThreshold?: number stickinessThreshold?: number
} }
@@ -41,7 +41,7 @@ export function useScrollManagement(
const lastScrollTopRef = useRef(0) const lastScrollTopRef = useRef(0)
const scrollBehavior = options?.behavior ?? 'smooth' const scrollBehavior = options?.behavior ?? 'smooth'
const stickinessThreshold = options?.stickinessThreshold ?? 100 const stickinessThreshold = options?.stickinessThreshold ?? 30
/** Scrolls the container to the bottom */ /** Scrolls the container to the bottom */
const scrollToBottom = useCallback(() => { const scrollToBottom = useCallback(() => {

View File

@@ -63,7 +63,6 @@ interface BlockEventHandlerConfig {
executionIdRef: { current: string } executionIdRef: { current: string }
workflowEdges: Array<{ id: string; target: string; sourceHandle?: string | null }> workflowEdges: Array<{ id: string; target: string; sourceHandle?: string | null }>
activeBlocksSet: Set<string> activeBlocksSet: Set<string>
activeBlockRefCounts: Map<string, number>
accumulatedBlockLogs: BlockLog[] accumulatedBlockLogs: BlockLog[]
accumulatedBlockStates: Map<string, BlockState> accumulatedBlockStates: Map<string, BlockState>
executedBlockIds: Set<string> executedBlockIds: Set<string>
@@ -310,7 +309,6 @@ export function useWorkflowExecution() {
executionIdRef, executionIdRef,
workflowEdges, workflowEdges,
activeBlocksSet, activeBlocksSet,
activeBlockRefCounts,
accumulatedBlockLogs, accumulatedBlockLogs,
accumulatedBlockStates, accumulatedBlockStates,
executedBlockIds, executedBlockIds,
@@ -330,18 +328,9 @@ export function useWorkflowExecution() {
const updateActiveBlocks = (blockId: string, isActive: boolean) => { const updateActiveBlocks = (blockId: string, isActive: boolean) => {
if (!workflowId) return if (!workflowId) return
if (isActive) { if (isActive) {
const count = activeBlockRefCounts.get(blockId) ?? 0
activeBlockRefCounts.set(blockId, count + 1)
activeBlocksSet.add(blockId) activeBlocksSet.add(blockId)
} else { } else {
const count = activeBlockRefCounts.get(blockId) ?? 1 activeBlocksSet.delete(blockId)
const next = count - 1
if (next <= 0) {
activeBlockRefCounts.delete(blockId)
activeBlocksSet.delete(blockId)
} else {
activeBlockRefCounts.set(blockId, next)
}
} }
setActiveBlocks(workflowId, new Set(activeBlocksSet)) setActiveBlocks(workflowId, new Set(activeBlocksSet))
} }
@@ -1291,7 +1280,6 @@ export function useWorkflowExecution() {
} }
const activeBlocksSet = new Set<string>() const activeBlocksSet = new Set<string>()
const activeBlockRefCounts = new Map<string, number>()
const streamedContent = new Map<string, string>() const streamedContent = new Map<string, string>()
const accumulatedBlockLogs: BlockLog[] = [] const accumulatedBlockLogs: BlockLog[] = []
const accumulatedBlockStates = new Map<string, BlockState>() const accumulatedBlockStates = new Map<string, BlockState>()
@@ -1304,7 +1292,6 @@ export function useWorkflowExecution() {
executionIdRef, executionIdRef,
workflowEdges, workflowEdges,
activeBlocksSet, activeBlocksSet,
activeBlockRefCounts,
accumulatedBlockLogs, accumulatedBlockLogs,
accumulatedBlockStates, accumulatedBlockStates,
executedBlockIds, executedBlockIds,
@@ -1915,7 +1902,6 @@ export function useWorkflowExecution() {
const accumulatedBlockStates = new Map<string, BlockState>() const accumulatedBlockStates = new Map<string, BlockState>()
const executedBlockIds = new Set<string>() const executedBlockIds = new Set<string>()
const activeBlocksSet = new Set<string>() const activeBlocksSet = new Set<string>()
const activeBlockRefCounts = new Map<string, number>()
try { try {
const blockHandlers = buildBlockEventHandlers({ const blockHandlers = buildBlockEventHandlers({
@@ -1923,7 +1909,6 @@ export function useWorkflowExecution() {
executionIdRef, executionIdRef,
workflowEdges, workflowEdges,
activeBlocksSet, activeBlocksSet,
activeBlockRefCounts,
accumulatedBlockLogs, accumulatedBlockLogs,
accumulatedBlockStates, accumulatedBlockStates,
executedBlockIds, executedBlockIds,
@@ -2119,7 +2104,6 @@ export function useWorkflowExecution() {
const workflowEdges = useWorkflowStore.getState().edges const workflowEdges = useWorkflowStore.getState().edges
const activeBlocksSet = new Set<string>() const activeBlocksSet = new Set<string>()
const activeBlockRefCounts = new Map<string, number>()
const accumulatedBlockLogs: BlockLog[] = [] const accumulatedBlockLogs: BlockLog[] = []
const accumulatedBlockStates = new Map<string, BlockState>() const accumulatedBlockStates = new Map<string, BlockState>()
const executedBlockIds = new Set<string>() const executedBlockIds = new Set<string>()
@@ -2131,7 +2115,6 @@ export function useWorkflowExecution() {
executionIdRef, executionIdRef,
workflowEdges, workflowEdges,
activeBlocksSet, activeBlocksSet,
activeBlockRefCounts,
accumulatedBlockLogs, accumulatedBlockLogs,
accumulatedBlockStates, accumulatedBlockStates,
executedBlockIds, executedBlockIds,

View File

@@ -39,7 +39,6 @@ export async function executeWorkflowWithFullLogging(
const workflowEdges = useWorkflowStore.getState().edges const workflowEdges = useWorkflowStore.getState().edges
const activeBlocksSet = new Set<string>() const activeBlocksSet = new Set<string>()
const activeBlockRefCounts = new Map<string, number>()
const payload: any = { const payload: any = {
input: options.workflowInput, input: options.workflowInput,
@@ -104,8 +103,6 @@ export async function executeWorkflowWithFullLogging(
switch (event.type) { switch (event.type) {
case 'block:started': { case 'block:started': {
const startCount = activeBlockRefCounts.get(event.data.blockId) ?? 0
activeBlockRefCounts.set(event.data.blockId, startCount + 1)
activeBlocksSet.add(event.data.blockId) activeBlocksSet.add(event.data.blockId)
setActiveBlocks(wfId, new Set(activeBlocksSet)) setActiveBlocks(wfId, new Set(activeBlocksSet))
@@ -118,14 +115,8 @@ export async function executeWorkflowWithFullLogging(
break break
} }
case 'block:completed': { case 'block:completed':
const completeCount = activeBlockRefCounts.get(event.data.blockId) ?? 1 activeBlocksSet.delete(event.data.blockId)
if (completeCount <= 1) {
activeBlockRefCounts.delete(event.data.blockId)
activeBlocksSet.delete(event.data.blockId)
} else {
activeBlockRefCounts.set(event.data.blockId, completeCount - 1)
}
setActiveBlocks(wfId, new Set(activeBlocksSet)) setActiveBlocks(wfId, new Set(activeBlocksSet))
setBlockRunStatus(wfId, event.data.blockId, 'success') setBlockRunStatus(wfId, event.data.blockId, 'success')
@@ -153,16 +144,9 @@ export async function executeWorkflowWithFullLogging(
options.onBlockComplete(event.data.blockId, event.data.output).catch(() => {}) options.onBlockComplete(event.data.blockId, event.data.output).catch(() => {})
} }
break break
}
case 'block:error': { case 'block:error':
const errorCount = activeBlockRefCounts.get(event.data.blockId) ?? 1 activeBlocksSet.delete(event.data.blockId)
if (errorCount <= 1) {
activeBlockRefCounts.delete(event.data.blockId)
activeBlocksSet.delete(event.data.blockId)
} else {
activeBlockRefCounts.set(event.data.blockId, errorCount - 1)
}
setActiveBlocks(wfId, new Set(activeBlocksSet)) setActiveBlocks(wfId, new Set(activeBlocksSet))
setBlockRunStatus(wfId, event.data.blockId, 'error') setBlockRunStatus(wfId, event.data.blockId, 'error')
@@ -187,7 +171,6 @@ export async function executeWorkflowWithFullLogging(
iterationContainerId: event.data.iterationContainerId, iterationContainerId: event.data.iterationContainerId,
}) })
break break
}
case 'execution:completed': case 'execution:completed':
executionResult = { executionResult = {

View File

@@ -122,25 +122,6 @@ export const ScheduleBlock: BlockConfig = {
required: true, required: true,
mode: 'trigger', mode: 'trigger',
condition: { field: 'scheduleType', value: 'custom' }, 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',
},
}, },
{ {

View File

@@ -604,7 +604,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
case 'send': { case 'send': {
baseParams.text = text baseParams.text = text
if (threadTs) { if (threadTs) {
baseParams.threadTs = threadTs baseParams.thread_ts = threadTs
} }
// files is the canonical param from attachmentFiles (basic) or files (advanced) // files is the canonical param from attachmentFiles (basic) or files (advanced)
const normalizedFiles = normalizeFileInput(files) const normalizedFiles = normalizeFileInput(files)

View File

@@ -40,7 +40,6 @@ export type GenerationType =
| 'neo4j-parameters' | 'neo4j-parameters'
| 'timestamp' | 'timestamp'
| 'timezone' | 'timezone'
| 'cron-expression'
export type SubBlockType = export type SubBlockType =
| 'short-input' // Single line input | 'short-input' // Single line input

View File

@@ -428,7 +428,7 @@ export class BlockExecutor {
block: SerializedBlock, block: SerializedBlock,
executionOrder: number executionOrder: number
): void { ): void {
const blockId = node.metadata?.originalBlockId ?? node.id const blockId = node.id
const blockName = block.metadata?.name ?? blockId const blockName = block.metadata?.name ?? blockId
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE
@@ -456,7 +456,7 @@ export class BlockExecutor {
executionOrder: number, executionOrder: number,
endedAt: string endedAt: string
): void { ): void {
const blockId = node.metadata?.originalBlockId ?? node.id const blockId = node.id
const blockName = block.metadata?.name ?? blockId const blockName = block.metadata?.name ?? blockId
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE

View File

@@ -827,10 +827,11 @@ export function formatParameterLabel(paramId: string): string {
} }
/** /**
* SubBlock IDs that control tool routing, not user-facing parameters. * SubBlock IDs that are "structural" — they control tool routing or auth,
* Excluded from tool-input rendering unless they have an explicit paramVisibility set. * not user-facing parameters. These are excluded from tool-input rendering
* unless they have an explicit paramVisibility set.
*/ */
const STRUCTURAL_SUBBLOCK_IDS = new Set(['operation']) const STRUCTURAL_SUBBLOCK_IDS = new Set(['operation', 'authMethod', 'destinationType'])
/** /**
* SubBlock types that represent auth/credential inputs handled separately * SubBlock types that represent auth/credential inputs handled separately
@@ -954,8 +955,12 @@ export function getSubBlocksForToolInput(
} else if (sb.id in toolParamVisibility) { } else if (sb.id in toolParamVisibility) {
visibility = toolParamVisibility[sb.id] visibility = toolParamVisibility[sb.id]
} else if (sb.canonicalParamId) { } 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' visibility = 'user-or-llm'
} else { } else {
// SubBlock has no corresponding tool param — skip it
continue continue
} }
} }

View File

@@ -57,7 +57,7 @@ export const slackMessageTool: ToolConfig<SlackMessageParams, SlackMessageRespon
visibility: 'user-or-llm', visibility: 'user-or-llm',
description: 'Message text to send (supports Slack mrkdwn formatting)', description: 'Message text to send (supports Slack mrkdwn formatting)',
}, },
threadTs: { thread_ts: {
type: 'string', type: 'string',
required: false, required: false,
visibility: 'user-or-llm', visibility: 'user-or-llm',
@@ -84,7 +84,7 @@ export const slackMessageTool: ToolConfig<SlackMessageParams, SlackMessageRespon
channel: isDM ? undefined : params.channel, channel: isDM ? undefined : params.channel,
userId: isDM ? params.dmUserId : params.userId, userId: isDM ? params.dmUserId : params.userId,
text: params.text, text: params.text,
thread_ts: params.threadTs || undefined, thread_ts: params.thread_ts || undefined,
files: params.files || null, files: params.files || null,
} }
}, },

View File

@@ -516,7 +516,7 @@ export interface SlackMessageParams extends SlackBaseParams {
dmUserId?: string dmUserId?: string
userId?: string userId?: string
text: string text: string
threadTs?: string thread_ts?: string
files?: UserFile[] files?: UserFile[]
} }