Skip to content

Commit 37a2a73

Browse files
committed
folder fix
1 parent caa454a commit 37a2a73

6 files changed

Lines changed: 89 additions & 6 deletions

File tree

apps/sim/lib/copilot/generated/tool-catalog-v1.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ export const CreateWorkflow: ToolCatalogEntry = {
439439
parameters: {
440440
type: 'object',
441441
properties: {
442-
folderId: { type: 'string', description: 'Optional folder ID.' },
442+
folderPath: {
443+
type: 'string',
444+
description:
445+
'Optional canonical workflow-folder VFS path copied from glob("workflows/**"), for example "workflows/Dream" or "workflows/Client%20Work/Intake". Omit for the workspace root.',
446+
},
443447
name: { type: 'string', description: 'Workflow name.' },
444448
workspaceId: { type: 'string', description: 'Optional workspace ID.' },
445449
},

apps/sim/lib/copilot/generated/tool-schemas-v1.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,10 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
221221
parameters: {
222222
type: 'object',
223223
properties: {
224-
folderId: {
224+
folderPath: {
225225
type: 'string',
226-
description: 'Optional folder ID.',
226+
description:
227+
'Optional canonical workflow-folder VFS path copied from glob("workflows/**"), for example "workflows/Dream" or "workflows/Client%20Work/Intake". Omit for the workspace root.',
227228
},
228229
name: {
229230
type: 'string',

apps/sim/lib/copilot/tools/handlers/param-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export interface GetBlockUpstreamReferencesParams {
3232
export interface CreateWorkflowParams {
3333
name?: string
3434
workspaceId?: string
35+
/** Canonical workflow-folder VFS path, for example `workflows/Dream`. */
36+
folderPath?: string
37+
/** Legacy executor input. New tool calls use folderPath and resolve the ID internally. */
3538
folderId?: string
3639
}
3740

apps/sim/lib/copilot/tools/handlers/workflow/mutations.test.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ vi.mock('../access', () => ({
124124

125125
import { applyCreateWorkflowOutputToContext } from '@/lib/copilot/request/tools/workflow-context'
126126
import { performUpdateWorkflow } from '@/lib/workflows/orchestration'
127-
import { verifyFolderWorkspace } from '@/lib/workflows/utils'
127+
import { listFolders, verifyFolderWorkspace } from '@/lib/workflows/utils'
128128
import {
129129
executeCreateWorkflow,
130130
executeMoveWorkflow,
@@ -136,6 +136,7 @@ import {
136136
} from './mutations'
137137

138138
const performUpdateWorkflowMock = vi.mocked(performUpdateWorkflow)
139+
const listFoldersMock = vi.mocked(listFolders)
139140
const verifyFolderWorkspaceMock = vi.mocked(verifyFolderWorkspace)
140141
const billingAttribution: BillingAttributionSnapshot = {
141142
actorUserId: 'user-1',
@@ -291,6 +292,7 @@ describe('executeCreateWorkflow billing attribution', () => {
291292
payerUsage: { currentUsage: 1, limit: 10 },
292293
})
293294
reserveExecutionSlotMock.mockResolvedValue({ reserved: true, created: true })
295+
listFoldersMock.mockResolvedValue([])
294296
})
295297

296298
it('ignores legacy description input instead of persisting it', async () => {
@@ -320,6 +322,65 @@ describe('executeCreateWorkflow billing attribution', () => {
320322
})
321323
})
322324

325+
it('canonicalizes a workflow-folder VFS path and resolves its internal ID', async () => {
326+
listFoldersMock.mockResolvedValue([
327+
{ folderId: 'folder-dream', folderName: 'Dream', parentId: null },
328+
{
329+
folderId: 'folder-launch-plans',
330+
folderName: 'Launch Plans',
331+
parentId: 'folder-dream',
332+
},
333+
])
334+
performCreateWorkflowMock.mockResolvedValue({
335+
success: true,
336+
workflow: {
337+
id: 'created-workflow',
338+
name: 'Created Workflow',
339+
workspaceId: 'workspace-1',
340+
folderId: 'folder-launch-plans',
341+
},
342+
})
343+
344+
const result = await executeCreateWorkflow(
345+
{
346+
name: 'Created Workflow',
347+
workspaceId: 'workspace-1',
348+
folderPath: 'workflows/Dream/Launch%20Plans',
349+
},
350+
executionContext
351+
)
352+
353+
expect(result.success).toBe(true)
354+
expect(performCreateWorkflowMock).toHaveBeenCalledWith({
355+
userId: 'user-1',
356+
workspaceId: 'workspace-1',
357+
name: 'Created Workflow',
358+
folderId: 'folder-launch-plans',
359+
})
360+
expect(workflowAuthzMockFns.mockAssertFolderMutable).toHaveBeenCalledWith('folder-launch-plans')
361+
})
362+
363+
it('fails clearly when a workflow-folder VFS path does not exist', async () => {
364+
listFoldersMock.mockResolvedValue([
365+
{ folderId: 'folder-existing', folderName: 'Existing', parentId: null },
366+
])
367+
368+
const result = await executeCreateWorkflow(
369+
{
370+
name: 'Created Workflow',
371+
workspaceId: 'workspace-1',
372+
folderPath: 'workflows/Dream',
373+
},
374+
executionContext
375+
)
376+
377+
expect(result).toEqual({
378+
success: false,
379+
error: 'Folder not found at workflows/Dream',
380+
})
381+
expect(performCreateWorkflowMock).not.toHaveBeenCalled()
382+
})
383+
323384
it('keeps same-workspace creation and subsequent execution on the immutable payer', async () => {
324385
const context: ExecutionContext = { ...executionContext, workflowId: '' }
325386
performCreateWorkflowMock.mockResolvedValue({

apps/sim/lib/copilot/tools/handlers/workflow/mutations.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,24 @@ export async function executeCreateWorkflow(
393393
}
394394
const workspaceId =
395395
params?.workspaceId || context.workspaceId || (await getDefaultWorkspaceId(context.userId))
396-
const folderId = params?.folderId || null
397396

398397
await ensureWorkspaceAccess(workspaceId, context.userId, 'write')
398+
399+
const folderPath = typeof params?.folderPath === 'string' ? params.folderPath.trim() : ''
400+
let folderId =
401+
typeof params?.folderId === 'string' && params.folderId.trim() ? params.folderId.trim() : null
402+
if (folderPath) {
403+
const relativePath = workflowFolderRelativePath(folderPath)
404+
if (!relativePath) {
405+
folderId = null
406+
} else {
407+
folderId = lookupFolderIdByPath(folderPath, await loadFolderPathToIdMap(workspaceId))
408+
if (!folderId) {
409+
return { success: false, error: `Folder not found at ${folderPath}` }
410+
}
411+
}
412+
}
413+
399414
await assertFolderMutable(folderId)
400415
assertWorkflowMutationNotAborted(context)
401416

bun.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)