diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 416eeff..f561a11 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -395,7 +395,7 @@ These tools are also EXTREMELY helpful for planning tasks, and for breaking down - **Proactiveness:** Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions. ${isNonInteractive ? '' :'- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If asked *how* to do something, explain first, don\'t just do it.'} - **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked. -- **Path Construction:** Before using any file system tool (e.g., ${ToolNames.READ_FILE}' or '${ToolNames.WRITE_FILE}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. +- **Path Construction:** Before using any file system tool (e.g., '${ToolNames.READ_FILE}' or '${ToolNames.WRITE_FILE}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. **CRITICAL: NEVER hallucinate paths from other environments or users (e.g., paths containing 'suyashpradhan', etc.). Use ONLY the project root directory provided in your context.** If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes. - **Verification & Testing:** You should verify that the changes done by you does not introduct any errors by building / compiling the changes. Then doing functional testing of the changes to ensure the implementation is successful and working accurately. diff --git a/packages/core/src/tools/read-data-file.ts b/packages/core/src/tools/read-data-file.ts index 422d247..d3b8bce 100644 --- a/packages/core/src/tools/read-data-file.ts +++ b/packages/core/src/tools/read-data-file.ts @@ -411,6 +411,7 @@ class ReadDataFileToolInvocation extends BaseToolInvocation< // Format the output for the LLM const llmContent = ` # Data File: ${relativePath} +(Absolute Path on this device: ${filePath}) ## File Information - **Type**: ${result.fileType} @@ -474,7 +475,7 @@ export class ReadDataFileTool extends BaseDeclarativeTool< properties: { absolute_path: { description: - "The absolute path to the data file to read and parse (e.g., '/home/user/project/data.csv'). Supported file types: .csv, .json, .txt, .xlsx, .xls, .docx, .doc. Relative paths are not supported.", + "The absolute path to the data file to read and parse (e.g., '/data.csv'). Supported file types: .csv, .json, .txt, .xlsx, .xls, .docx, .doc. Relative paths are not supported.", type: 'string', }, max_rows: { diff --git a/packages/core/src/tools/workspace-error-helper.test.ts b/packages/core/src/tools/workspace-error-helper.test.ts new file mode 100644 index 0000000..87d9616 --- /dev/null +++ b/packages/core/src/tools/workspace-error-helper.test.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { generateWorkspacePathError } from './workspace-error-helper.js'; + +describe('generateWorkspacePathError', () => { + const workspaceDirs = ['/Users/user/project']; + + it('should return a standard error message for normal paths outside workspace', () => { + const filePath = '/etc/passwd'; + const result = generateWorkspacePathError(filePath, workspaceDirs); + + expect(result).toContain('File path is outside the workspace'); + expect(result).toContain(`Requested path: ${filePath}`); + expect(result).not.toContain('ERROR: Path Hallucination Detected'); + }); + + it('should detect hallucinated path for "suyashpradhan"', () => { + const filePath = '/Users/suyashpradhan/Desktop/project/file.ts'; + const result = generateWorkspacePathError(filePath, workspaceDirs); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + expect(result).toContain('CRITICAL: You are attempting to use a path from your training data'); + expect(result).toContain('Your current PROJECT ROOT is: /Users/user/project'); + }); + + it('should detect generic unknown user paths (e.g., "sarah") as hallucination', () => { + const filePath = '/Users/sarah/Documents/data.csv'; + const result = generateWorkspacePathError(filePath, workspaceDirs); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + }); + + it('should detect Windows unknown user paths as hallucination', () => { + const winWorkspace = ['C:\\Users\\Eli\\Project']; + const filePath = 'C:\\Users\\John\\Desktop\\file.txt'; + const result = generateWorkspacePathError(filePath, winWorkspace); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + }); + + it('should detect Linux unknown user paths as hallucination', () => { + const linuxWorkspace = ['/home/ubuntu/project']; + const filePath = '/home/suyash/data.csv'; + const result = generateWorkspacePathError(filePath, linuxWorkspace); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + }); + + it('should NOT flag a hallucination if the project is in a folder named after a friend', () => { + // Scenario: User is 'eli', but has a project in a folder called 'richard_project' + const workspace = ['/Users/eli/Documents/richard_project']; + const filePath = '/Users/eli/Documents/richard_project/data.csv'; + + // This is just a normal out-of-workspace error (or valid if inside) + // But importantly, it's NOT a hallucination of the 'richard' user. + const result = generateWorkspacePathError(filePath, workspace); + expect(result).not.toContain('ERROR: Path Hallucination Detected'); + }); + + it('should NOT flag system paths (like /tmp) as hallucinations', () => { + const workspace = ['/Users/user/project']; + const filePath = '/tmp/debug.log'; + const result = generateWorkspacePathError(filePath, workspace); + + expect(result).toContain('File path is outside the workspace'); + expect(result).not.toContain('ERROR: Path Hallucination Detected'); + }); + + it('should detect hallucinated path for "blackbox-cli"', () => { + const filePath = '/some/path/blackbox-cli/package.json'; + const result = generateWorkspacePathError(filePath, workspaceDirs); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + }); + + it('should detect hallucinated data files (CSV) from other user directories', () => { + // Scenario: AI tries to use read_data_file on a CSV in a hallucinated folder + const filePath = '/Users/suyashpradhan/Downloads/subscriptions.csv'; + const result = generateWorkspacePathError(filePath, workspaceDirs); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + expect(result).toContain('Requested path: /Users/suyashpradhan/Downloads/subscriptions.csv'); + }); + + it('should detect hallucinated paths when the AI tries to write a file elsewhere', () => { + // Scenario: AI tries to use write_file to a different user's desktop + // We'll use a generic name like "Richard" which I've now generalized detection for + const filePath = 'C:\\Users\\Richard\\Desktop\\output.txt'; + const winWorkspace = ['C:\\Users\\User\\Project']; + const result = generateWorkspacePathError(filePath, winWorkspace); + + expect(result).toContain('ERROR: Path Hallucination Detected'); + }); + + it('should NOT detect "suyashpradhan" as hallucination if it is part of the actual workspace', () => { + const realWorkspace = ['/Users/suyashpradhan/my-docs']; + const filePath = '/Users/suyashpradhan/other-file.ts'; + const result = generateWorkspacePathError(filePath, realWorkspace); + + expect(result).toContain('File path is outside the workspace'); + expect(result).not.toContain('ERROR: Path Hallucination Detected'); + }); +}); diff --git a/packages/core/src/tools/workspace-error-helper.ts b/packages/core/src/tools/workspace-error-helper.ts index 2f06883..622d4a7 100644 --- a/packages/core/src/tools/workspace-error-helper.ts +++ b/packages/core/src/tools/workspace-error-helper.ts @@ -11,6 +11,41 @@ export function generateWorkspacePathError( filePath: string, workspaceDirectories: readonly string[], ): string { + // GENERIC CHECK: Detect if the path looks like it belongs to a different user profile. + // This catches hallucinations for ANY username on Windows, macOS, and Linux. + // Patterns: C:\Users\name, /Users/name, /home/name + const userDirPattern = /^([a-zA-Z]:\\Users\\[^\\]+)|^\/Users\/[^\/]+|^\/home\/[^\/]+/i; + const pathMatch = filePath.match(userDirPattern); + + let isHallucinatedPath = false; + + if (pathMatch) { + const matchedUserDir = pathMatch[0].toLowerCase(); + // We check if the matched "User Directory" exists anywhere in the valid workspace paths. + // This allows projects to be located deep within a user's home directory. + const isMatchedToRealUser = workspaceDirectories.some(dir => + dir.toLowerCase().includes(matchedUserDir) + ); + + if (!isMatchedToRealUser) { + isHallucinatedPath = true; + } + } + + // Fallback for specific known training data artifacts + if (!isHallucinatedPath && /blackbox-cli/i.test(filePath)) { + isHallucinatedPath = true; + } + + if (isHallucinatedPath) { + return `ERROR: Path Hallucination Detected. + +Requested path: ${filePath} +Your current PROJECT ROOT is: ${workspaceDirectories.join(', ')} + +CRITICAL: You are attempting to use a path from your training data or another user's environment. This path DOES NOT EXIST on this machine. Please reconstruct the path using the correct PROJECT ROOT mentioned above.`; + } + return `File path is outside the workspace Requested path: ${filePath} diff --git a/packages/core/src/utils/environmentContext.test.ts b/packages/core/src/utils/environmentContext.test.ts index aa436c6..ba60a32 100644 --- a/packages/core/src/utils/environmentContext.test.ts +++ b/packages/core/src/utils/environmentContext.test.ts @@ -46,7 +46,7 @@ describe('getDirectoryContextString', () => { it('should return context string for a single directory', async () => { const contextString = await getDirectoryContextString(mockConfig as Config); expect(contextString).toContain( - "I'm currently working in the directory: /test/dir", + "PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir", ); expect(contextString).toContain( 'Here is the folder structure of the current working directories:\n\nMock Folder Structure', @@ -119,11 +119,14 @@ describe('getEnvironmentContext', () => { expect(context).toContain("(formatted according to the user's locale)"); expect(context).toContain(`My operating system is: ${process.platform}`); expect(context).toContain( - "I'm currently working in the directory: /test/dir", + "PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir", ); expect(context).toContain( 'Here is the folder structure of the current working directories:\n\nMock Folder Structure', ); + expect(context).toContain( + 'IMPORTANT: All file paths you use MUST be relative to the PROJECT ROOT DIRECTORY or absolute paths starting with it. NEVER hallucinate paths from other environments.', + ); expect(getFolderStructure).toHaveBeenCalledWith('/test/dir', { fileService: undefined, }); diff --git a/packages/core/src/utils/environmentContext.ts b/packages/core/src/utils/environmentContext.ts index 73bc752..0c81786 100644 --- a/packages/core/src/utils/environmentContext.ts +++ b/packages/core/src/utils/environmentContext.ts @@ -31,7 +31,8 @@ export async function getDirectoryContextString( let workingDirPreamble: string; if (workspaceDirectories.length === 1) { - workingDirPreamble = `I'm currently working in the directory: ${workspaceDirectories[0]}`; + // Labeling this as PROJECT ROOT to provide a clear anchor for absolute path construction. + workingDirPreamble = `PROJECT ROOT DIRECTORY (Use this for all absolute paths): ${workspaceDirectories[0]}`; } else { const dirList = workspaceDirectories.map((dir) => ` - ${dir}`).join('\n'); workingDirPreamble = `I'm currently working in the following directories:\n${dirList}`; @@ -65,6 +66,8 @@ This is the Blackbox Code. We are setting up the context for our chat. Today's date is ${today} (formatted according to the user's locale). My operating system is: ${platform} ${directoryContext} + +IMPORTANT: All file paths you use MUST be relative to the PROJECT ROOT DIRECTORY or absolute paths starting with it. NEVER hallucinate paths from other environments. `.trim(); const initialParts: Part[] = [{ text: context }];