Skip to content

Commit 2db4e6c

Browse files
fix(custom-blocks): move executor tool id out of the custom-tool namespace
1 parent 4ab45cf commit 2db4e6c

7 files changed

Lines changed: 40 additions & 18 deletions

File tree

apps/sim/providers/custom-block-tool.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
import { beforeEach, describe, expect, it, vi } from 'vitest'
55
import { transformBlockTool } from '@/providers/utils'
6+
import { normalizeToolId } from '@/tools/normalize'
67

78
const mockResolve = vi.fn()
89

@@ -17,8 +18,8 @@ const options = {
1718
},
1819
],
1920
getTool: (id: string) =>
20-
id === 'custom_block_executor'
21-
? { id: 'custom_block_executor', description: 'exec' }
21+
id === 'deployed_block_executor'
22+
? { id: 'deployed_block_executor', description: 'exec' }
2223
: undefined,
2324
resolveCustomBlockBinding: mockResolve,
2425
}
@@ -28,7 +29,7 @@ describe('transformBlockTool — custom blocks', () => {
2829
vi.clearAllMocks()
2930
})
3031

31-
it('builds an id-keyed custom_block_executor tool and omits file[] fields', async () => {
32+
it('builds an id-keyed deployed_block_executor tool and omits file[] fields', async () => {
3233
mockResolve.mockResolvedValue({
3334
workflowId: 'wf-src',
3435
inputFields: [
@@ -45,7 +46,7 @@ describe('transformBlockTool — custom blocks', () => {
4546

4647
expect(tool).not.toBeNull()
4748
// Unique per block, name/description from the block (never the source workflow).
48-
expect(tool!.id).toBe('custom_block_executor_custom_block_test')
49+
expect(tool!.id).toBe('deployed_block_executor_custom_block_test')
4950
expect(tool!.name).toBe('The Elder')
5051
// Baked params: block type + assembled (id-keyed) input mapping.
5152
expect(tool!.params.blockType).toBe('custom_block_test')
@@ -59,6 +60,22 @@ describe('transformBlockTool — custom blocks', () => {
5960
expect(mockResolve).toHaveBeenCalledWith('custom_block_test')
6061
})
6162

63+
it('keeps the tool id out of the user-defined custom-tool namespace', async () => {
64+
mockResolve.mockResolvedValue({
65+
workflowId: 'wf-src',
66+
inputFields: [{ id: 'q', name: 'Question', type: 'string' }],
67+
requiredInputIds: [],
68+
})
69+
70+
const tool = await transformBlockTool({ type: 'custom_block_test', params: {} }, options)
71+
72+
// `custom_` is the custom-tool prefix (`isCustomTool`). Colliding with it makes
73+
// executeTool resolve via the DB custom-tool lookup, skip internal-field
74+
// stripping, and let `disableCustomTools` block deploy-as-block tools.
75+
expect(tool!.id.startsWith('custom_')).toBe(false)
76+
expect(normalizeToolId(tool!.id)).toBe('deployed_block_executor')
77+
})
78+
6279
it('does not offer the tool when a required file input has no preset value', async () => {
6380
mockResolve.mockResolvedValue({
6481
workflowId: 'wf-src',

apps/sim/providers/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ export async function transformBlockTool(
620620
// Custom (deploy-as-block) blocks resolve to the generic `workflow_executor`, but
621621
// as an agent tool they must run through the authority boundary (owner identity,
622622
// latest deployment, curated outputs) — not the plain workflow executor. Route
623-
// them to the dedicated in-process `custom_block_executor` tool, carrying the
623+
// them to the dedicated in-process `deployed_block_executor` tool, carrying the
624624
// block TYPE (never a source workflow id) so authority is re-resolved server-side.
625625
// Dynamic imports keep the DB/executor dependency graph out of client bundles.
626626
if (isCustomBlockType(block.type)) {
@@ -629,9 +629,9 @@ export async function transformBlockTool(
629629
logger.warn(`Custom block tool binding not resolved for type: ${block.type}`)
630630
return null
631631
}
632-
const customToolConfig = getTool('custom_block_executor')
632+
const customToolConfig = getTool('deployed_block_executor')
633633
if (!customToolConfig) {
634-
logger.warn('custom_block_executor tool not registered')
634+
logger.warn('deployed_block_executor tool not registered')
635635
return null
636636
}
637637
const inputMapping = assembleCustomBlockInputMapping(block.params || {})
@@ -653,7 +653,7 @@ export async function transformBlockTool(
653653
}
654654
return {
655655
// Unique per block so two custom-block tools never collide on the wire.
656-
id: `custom_block_executor_${block.type}`,
656+
id: `deployed_block_executor_${block.type}`,
657657
// Name/description come from the block itself — never the source workflow's
658658
// metadata, which the consumer has no access to.
659659
name: blockDef.name,

apps/sim/tools/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ export async function executeTool(
12421242
// tool registry never pulls in the executor/db dependency graph (a static or
12431243
// dynamic executor import in the tool descriptor itself would break the client
12441244
// build — and with it `getTool('workflow_executor')`).
1245-
if (normalizedToolId === 'custom_block_executor') {
1245+
if (normalizedToolId === 'deployed_block_executor') {
12461246
logger.info(`[${requestId}] Running custom block tool ${toolId}`)
12471247
const { runCustomBlockTool } = await import(
12481248
'@/executor/handlers/workflow/custom-block-tool-runner'

apps/sim/tools/normalize.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
* Pure string utility — no server dependencies, safe to import in client components.
88
*/
99
export function normalizeToolId(toolId: string): string {
10-
// Check the longer prefix first — `custom_block_executor_` also starts with
11-
// neither `workflow_executor_` nor a knowledge/table op, so ordering is safe,
12-
// but keep it explicit since its suffix (`custom_block_<id>`) is itself prefixed.
10+
// Custom (deploy-as-block) tools: 'deployed_block_executor_custom_block_<id>' ->
11+
// 'deployed_block_executor'. Note the id deliberately does NOT start with
12+
// `custom_` — that prefix is the user-defined custom-tool namespace
13+
// (`isCustomTool`), and colliding with it misroutes resolution and permissions.
1314
if (
14-
toolId.startsWith('custom_block_executor_') &&
15-
toolId.length > 'custom_block_executor_'.length
15+
toolId.startsWith('deployed_block_executor_') &&
16+
toolId.length > 'deployed_block_executor_'.length
1617
) {
17-
return 'custom_block_executor'
18+
return 'deployed_block_executor'
1819
}
1920

2021
if (toolId.startsWith('workflow_executor_') && toolId.length > 'workflow_executor_'.length) {

apps/sim/tools/registry.minimal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const tools: Record<string, ToolConfig> = {
9191
// Needed so workflow-as-tool and custom (deploy-as-block) tools resolve their
9292
// config in minimal-registry dev mode (both route through `workflow_executor`).
9393
workflow_executor: workflowExecutorTool,
94-
custom_block_executor: customBlockExecutorTool,
94+
deployed_block_executor: customBlockExecutorTool,
9595
gmail_send_v2: gmailSendV2Tool,
9696
gmail_read_v2: gmailReadV2Tool,
9797
gmail_search_v2: gmailSearchV2Tool,

apps/sim/tools/registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8185,7 +8185,7 @@ export const tools: Record<string, ToolConfig> = {
81858185
google_forms_delete_watch: googleFormsDeleteWatchTool,
81868186
google_forms_renew_watch: googleFormsRenewWatchTool,
81878187
workflow_executor: workflowExecutorTool,
8188-
custom_block_executor: customBlockExecutorTool,
8188+
deployed_block_executor: customBlockExecutorTool,
81898189
wealthbox_read_contact: wealthboxReadContactTool,
81908190
wealthbox_write_contact: wealthboxWriteContactTool,
81918191
wealthbox_read_task: wealthboxReadTaskTool,

apps/sim/tools/workflow/custom-block-executor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ interface CustomBlockExecutorParams {
1616
* custom-block branch returns first).
1717
*/
1818
export const customBlockExecutorTool: ToolConfig<CustomBlockExecutorParams> = {
19-
id: 'custom_block_executor',
19+
// NOT `custom_block_executor`: `custom_` is the user-defined custom-tool namespace
20+
// (`isCustomTool`), so that id would make `executeTool` resolve this through the
21+
// DB custom-tool lookup, skip `postProcessToolOutput`'s internal-field stripping,
22+
// and let `disableCustomTools` workspaces block deploy-as-block tools.
23+
id: 'deployed_block_executor',
2024
name: 'Custom Block Executor',
2125
description: 'Execute a published custom block (a workflow packaged as a reusable block).',
2226
version: '1.0.0',

0 commit comments

Comments
 (0)