Skip to content

Commit 4280949

Browse files
committed
fix(mship): manage_folder bug fixes
1 parent c8e8c50 commit 4280949

2 files changed

Lines changed: 36 additions & 538 deletions

File tree

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

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,35 +1260,37 @@ function workflowFolderRelativePath(rawPath: string): string {
12601260
}
12611261

12621262
/**
1263-
* Resolve a workflow-folder VFS path (e.g. `workflows/Marketing/Q3 Campaigns`)
1264-
* to its folderId by inverting the same {@link buildVfsFolderPathMap} the VFS
1265-
* uses to serve folder paths, so a path the agent sees via glob round-trips to
1266-
* the right id. Returns null for the workspace root or an unknown path.
1263+
* Load a lookup from each folder's canonical encoded VFS path to its id by
1264+
* inverting the same {@link buildVfsFolderPathMap} the VFS uses to serve folder
1265+
* paths, so a path the agent sees via glob round-trips to the right id. Fetched
1266+
* once per manage_folder call and reused across target + parent resolution.
12671267
*/
1268-
async function resolveWorkflowFolderIdByPath(
1269-
workspaceId: string,
1270-
rawPath: string
1271-
): Promise<string | null> {
1268+
async function loadFolderPathToIdMap(workspaceId: string): Promise<Map<string, string>> {
1269+
const byPath = new Map<string, string>()
1270+
for (const [folderId, encodedPath] of buildVfsFolderPathMap(
1271+
await listFolders(workspaceId)
1272+
).entries()) {
1273+
byPath.set(encodedPath, folderId)
1274+
}
1275+
return byPath
1276+
}
1277+
1278+
function lookupFolderIdByPath(rawPath: string, byPath: Map<string, string>): string | null {
12721279
const relative = workflowFolderRelativePath(rawPath)
12731280
if (!relative) return null
1274-
const canonical = encodeVfsPathSegments(decodeVfsPathSegments(relative))
1275-
const folders = await listFolders(workspaceId)
1276-
for (const [folderId, encodedPath] of buildVfsFolderPathMap(folders).entries()) {
1277-
if (encodedPath === canonical) return folderId
1278-
}
1279-
return null
1281+
return byPath.get(encodeVfsPathSegments(decodeVfsPathSegments(relative))) ?? null
12801282
}
12811283

12821284
/** Resolve the folder a manage_folder op targets, preferring folderId over path. */
12831285
async function resolveManageFolderTarget(
1284-
workspaceId: string,
1285-
params: ManageFolderParams
1286+
params: ManageFolderParams,
1287+
getFolderPaths: () => Promise<Map<string, string>>
12861288
): Promise<{ folderId: string } | { error: string }> {
12871289
const directId = typeof params.folderId === 'string' ? params.folderId.trim() : ''
12881290
if (directId) return { folderId: directId }
12891291
const path = typeof params.path === 'string' ? params.path.trim() : ''
12901292
if (!path) return { error: 'Provide the folder path (e.g. "workflows/Marketing") or folderId.' }
1291-
const folderId = await resolveWorkflowFolderIdByPath(workspaceId, path)
1293+
const folderId = lookupFolderIdByPath(path, await getFolderPaths())
12921294
if (!folderId) return { error: `Folder not found at ${path}` }
12931295
return { folderId }
12941296
}
@@ -1299,15 +1301,15 @@ async function resolveManageFolderTarget(
12991301
* (parentId null).
13001302
*/
13011303
async function resolveManageFolderParent(
1302-
workspaceId: string,
1303-
params: ManageFolderParams
1304+
params: ManageFolderParams,
1305+
getFolderPaths: () => Promise<Map<string, string>>
13041306
): Promise<{ parentId: string | null } | { error: string }> {
13051307
const directId = typeof params.parentId === 'string' ? params.parentId.trim() : ''
13061308
if (directId) return { parentId: directId }
13071309
if (params.parentId === null) return { parentId: null }
13081310
const dest = typeof params.destinationPath === 'string' ? params.destinationPath.trim() : ''
13091311
if (!dest || !workflowFolderRelativePath(dest)) return { parentId: null }
1310-
const parentId = await resolveWorkflowFolderIdByPath(workspaceId, dest)
1312+
const parentId = lookupFolderIdByPath(dest, await getFolderPaths())
13111313
if (!parentId) return { error: `Destination folder not found at ${dest}` }
13121314
return { parentId }
13131315
}
@@ -1326,6 +1328,12 @@ export async function executeManageFolder(
13261328
const operation = typeof params?.operation === 'string' ? params.operation.trim() : ''
13271329
const workspaceId = context.workspaceId || (await getDefaultWorkspaceId(context.userId))
13281330

1331+
// Fetch the workspace folder list at most once, lazily — only when a path
1332+
// (vs an explicit id) actually needs resolving, and shared across the
1333+
// target + parent lookups a single move/create performs.
1334+
let folderPathsPromise: Promise<Map<string, string>> | undefined
1335+
const getFolderPaths = () => (folderPathsPromise ??= loadFolderPathToIdMap(workspaceId))
1336+
13291337
switch (operation) {
13301338
case 'create': {
13311339
let name = typeof params.name === 'string' ? params.name.trim() : ''
@@ -1339,17 +1347,17 @@ export async function executeManageFolder(
13391347
name = segments[segments.length - 1]
13401348
const parentSegments = segments.slice(0, -1)
13411349
if (parentSegments.length > 0) {
1342-
const resolved = await resolveWorkflowFolderIdByPath(
1343-
workspaceId,
1344-
encodeVfsPathSegments(parentSegments)
1350+
const resolved = lookupFolderIdByPath(
1351+
encodeVfsPathSegments(parentSegments),
1352+
await getFolderPaths()
13451353
)
13461354
if (!resolved) {
13471355
return { success: false, error: `Parent folder not found for ${path}` }
13481356
}
13491357
parentId = resolved
13501358
}
13511359
} else {
1352-
const parent = await resolveManageFolderParent(workspaceId, params)
1360+
const parent = await resolveManageFolderParent(params, getFolderPaths)
13531361
if ('error' in parent) return { success: false, error: parent.error }
13541362
parentId = parent.parentId
13551363
}
@@ -1359,19 +1367,19 @@ export async function executeManageFolder(
13591367
case 'rename': {
13601368
const name = typeof params.name === 'string' ? params.name.trim() : ''
13611369
if (!name) return { success: false, error: 'rename requires a new name' }
1362-
const target = await resolveManageFolderTarget(workspaceId, params)
1370+
const target = await resolveManageFolderTarget(params, getFolderPaths)
13631371
if ('error' in target) return { success: false, error: target.error }
13641372
return executeRenameFolder({ folderId: target.folderId, name }, context)
13651373
}
13661374
case 'move': {
1367-
const target = await resolveManageFolderTarget(workspaceId, params)
1375+
const target = await resolveManageFolderTarget(params, getFolderPaths)
13681376
if ('error' in target) return { success: false, error: target.error }
1369-
const parent = await resolveManageFolderParent(workspaceId, params)
1377+
const parent = await resolveManageFolderParent(params, getFolderPaths)
13701378
if ('error' in parent) return { success: false, error: parent.error }
13711379
return executeMoveFolder({ folderId: target.folderId, parentId: parent.parentId }, context)
13721380
}
13731381
case 'delete': {
1374-
const target = await resolveManageFolderTarget(workspaceId, params)
1382+
const target = await resolveManageFolderTarget(params, getFolderPaths)
13751383
if ('error' in target) return { success: false, error: target.error }
13761384
return executeDeleteFolder({ folderIds: [target.folderId] }, context)
13771385
}

0 commit comments

Comments
 (0)