( directoryPath: string, permissionContext: ToolPermissionContext, )
| 29 | } |
| 30 | |
| 31 | export async function validateDirectoryForWorkspace( |
| 32 | directoryPath: string, |
| 33 | permissionContext: ToolPermissionContext, |
| 34 | ): Promise<AddDirectoryResult> { |
| 35 | if (!directoryPath) { |
| 36 | return { |
| 37 | resultType: 'emptyPath', |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | // resolve() strips the trailing slash expandPath can leave on absolute |
| 42 | // inputs, so /foo and /foo/ map to the same storage key (CC-33). |
| 43 | const absolutePath = resolve(expandPath(directoryPath)) |
| 44 | |
| 45 | // Check if path exists and is a directory (single syscall) |
| 46 | try { |
| 47 | const stats = await stat(absolutePath) |
| 48 | if (!stats.isDirectory()) { |
| 49 | return { |
| 50 | resultType: 'notADirectory', |
| 51 | directoryPath, |
| 52 | absolutePath, |
| 53 | } |
| 54 | } |
| 55 | } catch (e: unknown) { |
| 56 | const code = getErrnoCode(e) |
| 57 | // Match prior existsSync() semantics: treat any of these as "not found" |
| 58 | // rather than re-throwing. EACCES/EPERM in particular must not crash |
| 59 | // startup when a settings-configured additional directory is inaccessible. |
| 60 | if ( |
| 61 | code === 'ENOENT' || |
| 62 | code === 'ENOTDIR' || |
| 63 | code === 'EACCES' || |
| 64 | code === 'EPERM' |
| 65 | ) { |
| 66 | return { |
| 67 | resultType: 'pathNotFound', |
| 68 | directoryPath, |
| 69 | absolutePath, |
| 70 | } |
| 71 | } |
| 72 | throw e |
| 73 | } |
| 74 | |
| 75 | // Get current permission context |
| 76 | const currentWorkingDirs = allWorkingDirectories(permissionContext) |
| 77 | |
| 78 | // Check if already within an existing working directory |
| 79 | for (const workingDir of currentWorkingDirs) { |
| 80 | if (pathInWorkingPath(absolutePath, workingDir)) { |
| 81 | return { |
| 82 | resultType: 'alreadyInWorkingDirectory', |
| 83 | directoryPath, |
| 84 | workingDir, |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 |
no test coverage detected