Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,8 @@ export const PlusMenuDropdown = React.memo(
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<div
style={{
position: 'fixed',
left: anchorPos?.left ?? 0,
top: anchorPos?.top ?? 0,
width: 0,
height: 0,
pointerEvents: 'none',
}}
className='pointer-events-none fixed size-0'
style={{ left: anchorPos?.left ?? 0, top: anchorPos?.top ?? 0 }}
/>
</DropdownMenuTrigger>
<DropdownMenuContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,8 @@ export const SkillsMenuDropdown = React.memo(
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<div
style={{
position: 'fixed',
left: anchorPos?.left ?? 0,
top: anchorPos?.top ?? 0,
width: 0,
height: 0,
pointerEvents: 'none',
}}
className='pointer-events-none fixed size-0'
style={{ left: anchorPos?.left ?? 0, top: anchorPos?.top ?? 0 }}
/>
</DropdownMenuTrigger>
<DropdownMenuContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,17 @@ export function SkillInput({
const [editingSkillId, setEditingSkillId] = useState<string | null>(null)
const [editingSkillSnapshot, setEditingSkillSnapshot] = useState<SkillDefinition | null>(null)

const skillsById = useMemo(
() => new Map(workspaceSkills.map((skill) => [skill.id, skill])),
[workspaceSkills]
)

// Prefer the live query cache so the modal reflects concurrent edits, but
// fall back to the click-time snapshot when a background refetch drops the
// skill — otherwise the modal would close mid-edit and silently discard the
// draft; saving surfaces the real server error instead.
const editingSkill = editingSkillId
? (workspaceSkills.find((s) => s.id === editingSkillId) ?? editingSkillSnapshot)
? (skillsById.get(editingSkillId) ?? editingSkillSnapshot)
: null

const selectedSkills: StoredSkill[] = useMemo(() => {
Expand Down Expand Up @@ -119,10 +124,10 @@ export function SkillInput({

const resolveSkillName = useCallback(
(stored: StoredSkill): string => {
const found = workspaceSkills.find((s) => s.id === stored.skillId)
const found = skillsById.get(stored.skillId)
return found?.name ?? stored.name ?? stored.skillId
},
[workspaceSkills]
[skillsById]
)

return (
Expand All @@ -141,7 +146,7 @@ export function SkillInput({

{selectedSkills.length > 0 &&
selectedSkills.map((stored, index) => {
const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId)
const fullSkill = skillsById.get(stored.skillId)
const skillName = resolveSkillName(stored)
const workflowSearchHighlight = getWorkflowSearchLabelHighlight({
activeSearchTarget,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ export const ToolInput = memo(function ToolInput({
// subBlock): shown in the picker but greyed out with a tooltip instead of added.
const blockType = useWorkflowStore(useCallback((state) => state.blocks[blockId]?.type, [blockId]))
const unsupportedToolTypes = useMemo<readonly ('mcp' | 'custom-tool')[]>(() => {
const block = getAllBlocks().find((b) => b.type === blockType)
const block = blockType ? getBlock(blockType) : undefined
return block?.subBlocks.find((sb) => sb.id === subBlockId)?.unsupportedToolTypes ?? []
}, [blockType, subBlockId])
const mcpUnsupported = unsupportedToolTypes.includes('mcp')
Expand All @@ -529,9 +529,8 @@ export const ToolInput = memo(function ToolInput({
// Look up credential type for reactive condition filtering (e.g. service account detection).
// Uses canonical resolution so the active field (basic vs advanced) is respected.
const toolCredentialId = useMemo(() => {
const allBlocks = getAllBlocks()
for (const [toolIndex, tool] of selectedTools.entries()) {
const blockConfig = allBlocks.find((b: { type: string }) => b.type === tool.type)
const blockConfig = tool.type ? getBlock(tool.type) : undefined
if (!blockConfig?.subBlocks) continue
// canonical-index-unscoped: a nested tool resolves against `tool.params`, which only ever
// holds action-surface values — a tool is never invoked in trigger mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -640,13 +640,10 @@ export const Panel = memo(function Panel() {
setIsMenuOpen(false)
}, [collaborativeBatchToggleLocked])

// Compute run button state
const canRun = userPermissions.canRead // Running only requires read permissions
const canRun = userPermissions.canRead
const isLoadingPermissions = userPermissions.isLoading
const hasValidationErrors = false // TODO: Add validation logic if needed
const isWorkflowBlocked = isExecuting || hasValidationErrors
const isButtonDisabled =
!isExecuting && (isUsageGateLoading || isWorkflowBlocked || (!canRun && !isLoadingPermissions))
!isExecuting && (isUsageGateLoading || (!canRun && !isLoadingPermissions))

/**
* Register global keyboard shortcuts using the central commands registry.
Expand Down
5 changes: 2 additions & 3 deletions apps/sim/blocks/blocks/fireflies.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { omit } from '@sim/utils/object'
import { FirefliesIcon } from '@/components/icons'
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
Expand Down Expand Up @@ -698,9 +699,7 @@ Return ONLY the summary text - no quotes, no labels.`,
const firefliesV2SubBlocks = (FirefliesBlock.subBlocks || []).filter(
(subBlock) => subBlock.id !== 'audioUrl'
)
const firefliesV2Inputs = FirefliesBlock.inputs
? Object.fromEntries(Object.entries(FirefliesBlock.inputs).filter(([key]) => key !== 'audioUrl'))
: {}
const firefliesV2Inputs = FirefliesBlock.inputs ? omit(FirefliesBlock.inputs, ['audioUrl']) : {}

export const FirefliesV2Block: BlockConfig<FirefliesResponse> = {
...FirefliesBlock,
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/blocks/blocks/grain.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { omit } from '@sim/utils/object'
import { GrainIcon } from '@/components/icons'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
Expand Down Expand Up @@ -758,7 +759,7 @@ export const GrainV2Block: BlockConfig = {
},
},
inputs: {
...Object.fromEntries(Object.entries(GrainBlock.inputs).filter(([key]) => key !== 'viewId')),
...omit(GrainBlock.inputs, ['viewId']),
apiKey: { type: 'string', description: 'Grain API key (Personal or Workspace Access Token)' },
hookType: { type: 'string', description: 'Grain event type for the webhook' },
hookInclude: {
Expand Down
5 changes: 2 additions & 3 deletions apps/sim/blocks/blocks/stt.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { omit } from '@sim/utils/object'
import { STTIcon } from '@/components/icons'
import { AuthMode, type BlockConfig, IntegrationType } from '@/blocks/types'
import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils'
Expand Down Expand Up @@ -368,9 +369,7 @@ export const SttBlock: BlockConfig<SttBlockResponse> = {
},
}

const sttV2Inputs = SttBlock.inputs
? Object.fromEntries(Object.entries(SttBlock.inputs).filter(([key]) => key !== 'audioUrl'))
: {}
const sttV2Inputs = SttBlock.inputs ? omit(SttBlock.inputs, ['audioUrl']) : {}
const sttV2SubBlocks = (SttBlock.subBlocks || []).filter((subBlock) => subBlock.id !== 'audioUrl')

export const SttV2Block: BlockConfig<SttBlockResponse> = {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/executor/handlers/agent/agent-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { selectModelBoundFileInputPaths } from '@/lib/uploads/utils/model-input'
import { hydrateUserFilesWithBase64 } from '@/lib/uploads/utils/user-file-base64.server'
import { resolveCustomBlockToolBinding } from '@/lib/workflows/custom-blocks/operations'
import { getCustomToolById } from '@/lib/workflows/custom-tools/operations'
import { getAllBlocks } from '@/blocks'
import { getAllBlocks, getBlock } from '@/blocks'
import { assembleCustomBlockInputMapping, isCustomBlockType } from '@/blocks/custom/build-config'
import type { BlockOutput } from '@/blocks/types'
import { normalizeFileInput } from '@/blocks/utils'
Expand Down Expand Up @@ -857,7 +857,7 @@ export class AgentBlockHandler implements BlockHandler {
)
if (tool.type === 'mcp' || tool.type === 'custom-tool') return alignedParams

const blockInputs = getAllBlocks().find((block) => block.type === tool.type)?.inputs
const blockInputs = tool.type ? getBlock(tool.type)?.inputs : undefined
return prepareResolvedSecretProjectedInputs(alignedParams, blockInputs, formattedParams)
}

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/executor/handlers/api/api-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ApiBlockHandler implements BlockHandler {
if (trimmedBody.startsWith('{') || trimmedBody.startsWith('[')) {
processedInputs.body = JSON.parse(trimmedBody)
}
} catch (e) {}
} catch {}
} else if (processedInputs.body === null) {
processedInputs.body = undefined
}
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/executor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class StreamingResponseFormatProcessor implements ResponseFormatStreamPro

return null
}
} catch (e) {}
} catch {}

const openBraces = (buffer.match(/\{/g) || []).length
const closeBraces = (buffer.match(/\}/g) || []).length
Expand All @@ -138,7 +138,7 @@ export class StreamingResponseFormatProcessor implements ResponseFormatStreamPro

return null
}
} catch (e) {}
} catch {}
}

return null
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/executor/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getErrorMessage } from '@sim/utils/errors'
import { HttpError } from '@/lib/core/utils/http-error'
import type { ExecutionContext, ExecutionResult } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'
Expand Down Expand Up @@ -49,8 +50,7 @@ export interface BlockExecutionErrorDetails {
* every block boundary.
*/
export function buildBlockExecutionError(details: BlockExecutionErrorDetails): Error {
const errorMessage =
details.error instanceof Error ? details.error.message : String(details.error)
const errorMessage = getErrorMessage(details.error)
const blockName = details.block.metadata?.name || details.block.id
const blockType = details.block.metadata?.id || 'unknown'

Expand Down
8 changes: 6 additions & 2 deletions apps/sim/hooks/selectors/providers/google/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { requestJson } from '@/lib/api/client/request'
import * as selectorContracts from '@/lib/api/contracts/selectors'
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
import {
ensureCredential,
SELECTOR_SEARCH_STALE,
SELECTOR_STALE,
} from '@/hooks/selectors/providers/shared'
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'

export const googleSelectors = {
Expand Down Expand Up @@ -101,7 +105,7 @@ export const googleSelectors = {
selectorContracts.googleDriveFilesSelectorContract,
selectorContracts.googleDriveFileSelectorContract,
],
staleTime: 15 * 1000,
staleTime: SELECTOR_SEARCH_STALE,
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'google.drive',
Expand Down
9 changes: 7 additions & 2 deletions apps/sim/hooks/selectors/providers/jira/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { requestJson } from '@/lib/api/client/request'
import * as selectorContracts from '@/lib/api/contracts/selectors'
import { fetchOAuthToken } from '@/hooks/selectors/helpers'
import { ensureCredential, ensureDomain, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
import {
ensureCredential,
ensureDomain,
SELECTOR_SEARCH_STALE,
SELECTOR_STALE,
} from '@/hooks/selectors/providers/shared'
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'

export const jiraSelectors = {
Expand Down Expand Up @@ -71,7 +76,7 @@ export const jiraSelectors = {
selectorContracts.jiraIssuesSelectorContract,
selectorContracts.jiraIssueSelectorContract,
],
staleTime: 15 * 1000,
staleTime: SELECTOR_SEARCH_STALE,
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'jira.issues',
Expand Down
11 changes: 11 additions & 0 deletions apps/sim/hooks/selectors/providers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'

export const SELECTOR_STALE = 60 * 1000

/**
* The shorter stale window carried by `google.drive`, `jira.issues` and
* `webflow.items`, whose listings turn over faster than {@link SELECTOR_STALE}
* assumes.
*
* Not every search-backed selector uses it — several still sit on
* {@link SELECTOR_STALE} — so treat this as the value those three share rather
* than a rule about search.
*/
export const SELECTOR_SEARCH_STALE = 15 * 1000

export const ensureCredential = (context: SelectorContext, key: SelectorKey): string => {
if (!context.oauthCredential) {
throw new Error(`Missing credential for selector ${key}`)
Expand Down
8 changes: 6 additions & 2 deletions apps/sim/hooks/selectors/providers/webflow/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { requestJson } from '@/lib/api/client/request'
import * as selectorContracts from '@/lib/api/contracts/selectors'
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
import {
ensureCredential,
SELECTOR_SEARCH_STALE,
SELECTOR_STALE,
} from '@/hooks/selectors/providers/shared'
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'

export const webflowSelectors = {
Expand Down Expand Up @@ -59,7 +63,7 @@ export const webflowSelectors = {
'webflow.items': {
key: 'webflow.items',
contracts: [selectorContracts.webflowItemsSelectorContract],
staleTime: 15 * 1000,
staleTime: SELECTOR_SEARCH_STALE,
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'webflow.items',
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/lib/workflows/subblocks/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,13 +556,15 @@ export function resolveSkillsLabel(
if (subBlock?.type !== 'skill-input') return null
if (!Array.isArray(rawValue) || rawValue.length === 0) return null

const skillsById = new Map(skills.map((skill) => [skill.id, skill]))

const names = rawValue
.map((skill: unknown) => {
if (!skill || typeof skill !== 'object') return null
const s = skill as { skillId?: string; name?: string }

if (s.skillId) {
const found = skills.find((candidate) => candidate.id === s.skillId)
const found = skillsById.get(s.skillId)
if (found?.name) return found.name
}
if (typeof s.name === 'string' && s.name) return s.name
Expand Down
Loading